复制和移动儿童成语类?
Copy and Move Idiom in children classes?
class A
{
std::string val1;
A(std::string str) : val1(std::move(str)){}
};
class B: A
{
B(std::string str) : A(str){}
};
这样的话,str
会被复制两次,还是不会?
在儿童 类 中使用复制和移动习语的最佳方法是什么?
在这种情况下,确定的最佳方法是经常记录副本和移动。只需将 std::string
包装到自定义 class 或具有自定义复制和移动语义的结构中,如下所示:
class StringWrapper
{
public:
std::string data;
StringWrapper(const char* cstr)
: data(cstr) {}
// Copy constructor and assignment
StringWrapper(const StringWrapper& other)
: data(other.data)
{
std::cout << "Copy constructor!\n";
}
StringWrapper& operator=(const StringWrapper& other)
{
data = other.data;
std::cout << "Copy assignment!\n";
return *this;
}
// Move constructor and assignment
StringWrapper(StringWrapper&& other)
: data(std::move(other.data))
{
std::cout << "Move constructor!\n";
}
StringWrapper& operator=(StringWrapper&& other)
{
data = std::move(other.data);
std::cout << "Move assignment!\n";
return *this;
}
};
然后用StringWrapper
代替std::string
的每次使用:
class A
{
public: // Public for testing
StringWrapper val1;
A(StringWrapper str) : val1(std::move(str)){}
};
class B: public A // Private inheritance should be avoided
{
public: // Public for testing
B(StringWrapper str) : A(std::move(str)){}
};
经过小测试:
int main()
{
StringWrapper strA("hello");
A a(strA);
std::cout << a.val1.data << '\n';
StringWrapper strB("world");
B b(strB);
std::cout << b.val1.data << '\n';
}
然后分析输出:
Copy constructor!
Move constructor!
hello
Copy constructor!
Copy constructor!
Move constructor!
world
第一次复制发生在按值传递 strA
时。这是必要的,因为我们不希望对 strA
的更改影响 a.val1
。
第二个副本非常相似:它发生在按值传递 strB
时。出于同样的原因,这也是必要的。
另一方面,第三个副本是多余的。它发生在将 str
传递给 B
的构造函数中的父构造函数时。 str
会在构建结束时销毁,所以我们最好移动它的内容而不是复制它:
class B: public A
{
public:
B(StringWrapper str) : A(std::move(str)){} // Replaced copy with move
};
已修复!
Copy constructor!
Move constructor!
hello
Copy constructor!
Move constructor!
Move constructor!
world
如果您是 C++ 的新手,最好简单地调查一下如果您执行您的代码会发生什么,而不是做出可能错误的假设。
要查看发生了什么,只需将 std::string
替换为您自己的 class 并在其中放入一些调试输出。
struct mystring
{
mystring() { std::cout << "Default" << std::endl; }
mystring( mystring&& ) { std::cout << "Move" << std::endl; }
mystring( const mystring& ) { std::cout << "Copy" << std::endl; }
mystring& operator=( const mystring& ) { std::cout << "Copy assign" << std::endl; return *this; }
mystring& operator=( mystring&& ) { std::cout << "Move assign" << std::endl; return *this; }
~mystring() { std::cout << "Delete" << std::endl; }
};
还有一些测试代码
int main()
{
std::cout << "1" << std::endl;
mystring ms;
std::cout << "2" << std::endl;
B b(std::move(ms));
std::cout << "3" << std::endl;
}
您将看到:
1
Default
2
Copy
Copy
Move
Delete
Delete
3
Delete
Delete
您的问题“在这种情况下,str 是否会被复制两次?”被回答:是的,它复制了两次!
如果您还希望 forwarding/move 按预期工作,则必须始终遵循 rule of three/five/zero。
这将导致:
class A
{
private:
mystring val1;
public:
A(): val1{}{}
A( const mystring& str ): val1( str ) {}
A(mystring&& str) : val1(std::move(str)){}
// if you also want to assign a string later
A& operator=(const mystring& str ) { std::cout << "copy string to A" << std::endl; val1 = str; return *this; }
A& operator=(mystring&& str ) { std::cout << "move string to A" << std::endl; val1 = (std::move(str)); return *this; }
// and now all the stuff for copy/move of class itself:
A( const A& a): val1{ a.val1} {}
A( A&& a): val1{ std::move(a.val1)} {}
A& operator=( const A& a) { std::cout << "copy from A" << std::endl; val1=a.val1; return *this; }
A& operator=( A&& a) { std::cout << "move from A" << std::endl; val1=std::move(a.val1); return *this; }
};
class B: public A
{
public:
B(): A{}{}
B( const mystring& str ): A( str ) {}
B(mystring&& str) : A(std::move(str)){}
// and now all the stuff for copy/move of class itself:
B( const B& b): A{ b} {}
B( B&& b): A{ std::move(b)} {}
B& operator=( const B& b) { std::cout << "copy from B" << std::endl; A::operator=(b); return *this; }
B& operator=( B&& b) { std::cout << "move from B" << std::endl; A::operator=(std::move(b)); return *this; }
};
现在还要测试这个:
int main()
{
std::cout << "1" << std::endl;
mystring ms;
std::cout << "2" << std::endl;
B b(std::move(ms));
std::cout << "3" << std::endl;
//
std::cout << "4" << std::endl;
B b2;
// and now move assign
std::cout << "5" << std::endl;
b = std::move(b2);
std::cout << "6" << std::endl;
}
结果:
1
Default
2
Move
3
4
Default
5
move from B
move from A
Move assign
6
Delete
Delete
Delete
现在第一步只有一个动作,动作分配也按预期工作。
看到了运行here
class A
{
std::string val1;
A(std::string str) : val1(std::move(str)){}
};
class B: A
{
B(std::string str) : A(str){}
};
这样的话,str
会被复制两次,还是不会?
在儿童 类 中使用复制和移动习语的最佳方法是什么?
在这种情况下,确定的最佳方法是经常记录副本和移动。只需将 std::string
包装到自定义 class 或具有自定义复制和移动语义的结构中,如下所示:
class StringWrapper
{
public:
std::string data;
StringWrapper(const char* cstr)
: data(cstr) {}
// Copy constructor and assignment
StringWrapper(const StringWrapper& other)
: data(other.data)
{
std::cout << "Copy constructor!\n";
}
StringWrapper& operator=(const StringWrapper& other)
{
data = other.data;
std::cout << "Copy assignment!\n";
return *this;
}
// Move constructor and assignment
StringWrapper(StringWrapper&& other)
: data(std::move(other.data))
{
std::cout << "Move constructor!\n";
}
StringWrapper& operator=(StringWrapper&& other)
{
data = std::move(other.data);
std::cout << "Move assignment!\n";
return *this;
}
};
然后用StringWrapper
代替std::string
的每次使用:
class A
{
public: // Public for testing
StringWrapper val1;
A(StringWrapper str) : val1(std::move(str)){}
};
class B: public A // Private inheritance should be avoided
{
public: // Public for testing
B(StringWrapper str) : A(std::move(str)){}
};
经过小测试:
int main()
{
StringWrapper strA("hello");
A a(strA);
std::cout << a.val1.data << '\n';
StringWrapper strB("world");
B b(strB);
std::cout << b.val1.data << '\n';
}
然后分析输出:
Copy constructor!
Move constructor!
hello
Copy constructor!
Copy constructor!
Move constructor!
world
第一次复制发生在按值传递 strA
时。这是必要的,因为我们不希望对 strA
的更改影响 a.val1
。
第二个副本非常相似:它发生在按值传递 strB
时。出于同样的原因,这也是必要的。
另一方面,第三个副本是多余的。它发生在将 str
传递给 B
的构造函数中的父构造函数时。 str
会在构建结束时销毁,所以我们最好移动它的内容而不是复制它:
class B: public A
{
public:
B(StringWrapper str) : A(std::move(str)){} // Replaced copy with move
};
已修复!
Copy constructor!
Move constructor!
hello
Copy constructor!
Move constructor!
Move constructor!
world
如果您是 C++ 的新手,最好简单地调查一下如果您执行您的代码会发生什么,而不是做出可能错误的假设。
要查看发生了什么,只需将 std::string
替换为您自己的 class 并在其中放入一些调试输出。
struct mystring
{
mystring() { std::cout << "Default" << std::endl; }
mystring( mystring&& ) { std::cout << "Move" << std::endl; }
mystring( const mystring& ) { std::cout << "Copy" << std::endl; }
mystring& operator=( const mystring& ) { std::cout << "Copy assign" << std::endl; return *this; }
mystring& operator=( mystring&& ) { std::cout << "Move assign" << std::endl; return *this; }
~mystring() { std::cout << "Delete" << std::endl; }
};
还有一些测试代码
int main()
{
std::cout << "1" << std::endl;
mystring ms;
std::cout << "2" << std::endl;
B b(std::move(ms));
std::cout << "3" << std::endl;
}
您将看到:
1
Default
2
Copy
Copy
Move
Delete
Delete
3
Delete
Delete
您的问题“在这种情况下,str 是否会被复制两次?”被回答:是的,它复制了两次!
如果您还希望 forwarding/move 按预期工作,则必须始终遵循 rule of three/five/zero。
这将导致:
class A
{
private:
mystring val1;
public:
A(): val1{}{}
A( const mystring& str ): val1( str ) {}
A(mystring&& str) : val1(std::move(str)){}
// if you also want to assign a string later
A& operator=(const mystring& str ) { std::cout << "copy string to A" << std::endl; val1 = str; return *this; }
A& operator=(mystring&& str ) { std::cout << "move string to A" << std::endl; val1 = (std::move(str)); return *this; }
// and now all the stuff for copy/move of class itself:
A( const A& a): val1{ a.val1} {}
A( A&& a): val1{ std::move(a.val1)} {}
A& operator=( const A& a) { std::cout << "copy from A" << std::endl; val1=a.val1; return *this; }
A& operator=( A&& a) { std::cout << "move from A" << std::endl; val1=std::move(a.val1); return *this; }
};
class B: public A
{
public:
B(): A{}{}
B( const mystring& str ): A( str ) {}
B(mystring&& str) : A(std::move(str)){}
// and now all the stuff for copy/move of class itself:
B( const B& b): A{ b} {}
B( B&& b): A{ std::move(b)} {}
B& operator=( const B& b) { std::cout << "copy from B" << std::endl; A::operator=(b); return *this; }
B& operator=( B&& b) { std::cout << "move from B" << std::endl; A::operator=(std::move(b)); return *this; }
};
现在还要测试这个:
int main()
{
std::cout << "1" << std::endl;
mystring ms;
std::cout << "2" << std::endl;
B b(std::move(ms));
std::cout << "3" << std::endl;
//
std::cout << "4" << std::endl;
B b2;
// and now move assign
std::cout << "5" << std::endl;
b = std::move(b2);
std::cout << "6" << std::endl;
}
结果:
1
Default
2
Move
3
4
Default
5
move from B
move from A
Move assign
6
Delete
Delete
Delete
现在第一步只有一个动作,动作分配也按预期工作。
看到了运行here