我试图理解移动语义和复制/移动省略.
我想要一个包含一些数据的类.我想在构造函数中传递数据,我想拥有数据.
看完这个,这个和这个给我的感觉是,在C++ 11,如果我想保存一个副本,然后从增加的代码大小的小问题,通过按值应至少与任何其他的选择,因为有效的(除).
然后,如果调用代码想要避免复制,则可以通过传递右值而不是左值.(例如使用std :: move)
所以我试了一下:
#include <iostream>
struct Data {
Data() { std::cout << " constructor\n";}
Data(const Data& data) { std::cout << " copy constructor\n";}
Data(Data&& data) { std::cout << " move constructor\n";}
};
struct DataWrapperWithMove {
Data data_;
DataWrapperWithMove(Data&& data) : data_(std::move(data)) { }
};
struct DataWrapperByValue {
Data data_;
DataWrapperByValue(Data data) : data_(std::move(data)) { }
};
Data
function_returning_data() {
Data d;
return d;
}
int main() {
std::cout << …Run Code Online (Sandbox Code Playgroud) I would expect that in C++20 the following code prints nothing between prints of A and B (since I expect guaranteed RVO to kick in). But output is:
A
Bye
B
C
Bye
Bye
So presumably one temporary is being created.
#include <iostream>
#include <tuple>
struct INeedElision{
int i;
~INeedElision(){
std::cout << "Bye\n";
}
};
std::tuple<int, INeedElision> f(){
int i = 47;
return {i, {47}};
}
INeedElision g(){
return {};
}
int main()
{
std::cout << "A\n";
auto x = …Run Code Online (Sandbox Code Playgroud) 考虑以下 C++ >=17 中的示例代码:
struct A{
A() = default;
A(const A&) = delete;
};
const A f(){ return A{}; }
int main(){
const A& a = f(); // OK
// A& b = f(); // Error: cannot convert 'const A' to 'A&'
const A c = f(); // OK: Copy elision
A d = f(); // OK!?
}
Run Code Online (Sandbox Code Playgroud)
该类A是不可复制的,但由于强制复制省略,我们可以将 的结果f()放入变量中。根据cppreference.com 中的这个页面,上述行为是完全合法的,因为它指定const在发生复制省略时忽略返回值上的量词。
然而,这种行为对我来说似乎非常违反直觉。由于A是不可复制的,我觉得应该没有办法const A变成A(除非你有A::A(const …
这段代码:
#include <vector>
std::vector<float> getstdvec() {
std::vector<float> v(4);
v[0] = 1;
v[1] = 2;
v[2] = 3;
v[3] = 4;
return v;
}
int main() {
std::vector<float> v(4);
for (int i = 0; i != 1000; ++i)
{
v = getstdvec();
}
}
Run Code Online (Sandbox Code Playgroud)
我在这里的错误理解是函数getstdvec不应该实际分配它返回的向量.当我在valgrind/callgrind中运行它时,我看到有1001个调用malloc; 1表示main中的初始向量声明,1000表示每次循环迭代.
是什么赋予了?如何从这样的函数返回向量(或任何其他对象)而不必每次都分配它?
编辑:我知道我可以通过引用传递向量.我的印象是,有可能(甚至更可取)编写这样的函数,它返回一个对象而不会产生不必要的分配.
给定
struct E
{
};
struct P
{
explicit P(E) {}
};
struct L
{
operator E() {return {};}
operator P() {return P{E{}};}
};
Run Code Online (Sandbox Code Playgroud)
根据C ++ 17语言标准,该表达式应该P{L{}}编译吗?
不同的编译器产生不同的结果:
以下代码使用 MSVC (/permissive-) 编译,但无法使用 GCC/Clang 编译 m_ptr1 和 m_ptr2。
#include <memory>
struct ForwardDeclared;
class A {
public:
explicit A();
~A();
private:
std::unique_ptr<ForwardDeclared> m_ptr1 = nullptr; // not ok
std::unique_ptr<ForwardDeclared> m_ptr2 {std::unique_ptr<ForwardDeclared>{}}; // not ok
std::unique_ptr<ForwardDeclared> m_ptr3 {nullptr}; // ok
std::unique_ptr<ForwardDeclared> m_ptr4; // ok
};
int main() {
A a;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我的理解是该=符号会导致复制初始化,但是,由于复制省略,我预计m_ptr2仍会被初始化而不会失败。
为什么m_ptr2需要 ForwardDeclared 的析构函数并且 Clang/GCC 对此是否正确?(奖励:得出 m_ptr1 被 MSVC 错误接受的结论是否正确?)
我有一个简单的代码:
#include <atomic>
int main()
{
std::atomic<int> a = 0;
}
Run Code Online (Sandbox Code Playgroud)
这段代码可以在 GCC 11.1.0 和 -std=c++17 下正常编译,但在 -std=c++14 和 -std=c++11 时失败。
使用删除的函数 std::atomic::atomic(const std::atomic&)
这是为什么?在 C++17 类中std::atomic仍然没有复制构造函数。为什么此代码对 -std=c++17 有效?
当然,我知道首选样式是 use {},但我很好奇为什么上面的代码从 C++17 开始就可以很好地编译。
std::unique_ptr<int> ptr() {
std::unique_ptr<int> p(new int(3));
return p; // Why doesn't this require explicit move using std::move?
} // Why didn't the data pointed to by 'p' is not destroyed here though p is not moved?
int main() {
std::unique_ptr<int> a = ptr(); // Why doesn't this require std::move?
std::cout << *a; // Prints 3.
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,函数ptr()返回一个副本p.如果p超出范围,数据"3"应被删除.但是代码如何在没有任何访问冲突的情况下工作?
在c ++草案n4606 [dcl.init] 17.6中有一段关于保证副本省略的段落:
- 如果目标类型是(可能是cv限定的)类类型:
- 如果初始化表达式是prvalue且源类型的cv-nonqualified版本与目标类相同,则初始化表达式用于初始化目标对象.[ 示例:
T x = T(T(T()));调用T默认构造函数进行初始化x.- 结束例子 ]- [...]
还有关于它如何运作的问答报告.
对我自己的理解,我引用的规则保证当初始化表达式是prvalue并且源类型的cv-nonqualified版本与目标类相同时,不应该涉及ctors.所以不需要检查复制的存在或移动ctor,这使得下面的代码在C++ 17中是合法的:
struct A {
A() {}
A(A const &) = delete;
A(A &&) = delete;
};
A f() { return A(); } // it's illegal in C++14, and suppose to be legal in C++17
Run Code Online (Sandbox Code Playgroud)
然而,令我发疯的是我在c ++ draft n4606的list-initialization部分找不到类似的规则.我发现的是([dcl.init.list] 3.6)
[...]
- 否则,如果
T是类类型,则考虑构造函数.枚举适用的构造函数,并通过重载决策(13.3,13.3.1.7)选择最佳构造函数.如果转换任何参数需要缩小转换(见下文),则程序格式错误.[...]
由于列表初始化的优先级高于我引用的第一个规则,因此当初始化程序是初始化程序列表时,我们应该考虑列表初始化部分中的规则.我们可以看到,在列表初始化类类型时会考虑构造函数T.所以,继续前面的例子,将
A ff() { return {A()}; }
Run Code Online (Sandbox Code Playgroud)
在C++ 17中合法吗?有人可以找到标准草案指定保证副本省略在列表初始化中如何工作的地方吗?
假设我有一个奇怪的字符串类型,拥有或不拥有它的底层缓冲区:
class WeirdString {
private:
char* buffer;
size_t length;
size_t capacity;
bool owns;
public:
// Non-owning constructor
WeirdString(char* buffer, size_t length, size_t capacity)
: buffer(buffer), length(length), capacity(capacity), owns(false)
{ }
// Make an owning copy
WeirdString(WeirdString const& rhs)
: buffer(new char[rhs.capacity])
, length(rhs.length)
, capacity(rhs.capacity)
, owns(true)
{
memcpy(buffer, rhs.buffer, length);
}
~WeirdString() {
if (owns) delete [] buffer;
}
};
Run Code Online (Sandbox Code Playgroud)
复制构造函数是否违反了某个标准?考虑:
WeirdString get(); // this returns non-owning string
const auto s = WeirdString(get());
Run Code Online (Sandbox Code Playgroud)
s是拥有还是非拥有,具体取决于附加的复制构造函数是否被省略,在C++ 14及更早版本中是允许的但是可选的(尽管在C++ 17中是有保证的).Schrödinger的所有权模型表明这个拷贝构造函数本身就是未定义的行为.
是吗?
一个更具说明性的例子可能是: …