我试图找出何时使用移动语义以及何时使用复制构造函数和赋值运算符作为经验法则.你的类中使用的指针类型(如果有的话)似乎受到这个答案的影响,所以我已经包含了这个.
没有指针 - 基于这个答案,如果你有一个包含int和string等基本类型的POD类,则不需要编写自定义移动或复制构造函数和运算符.
unique-ptr - 基于这个答案,当使用移动语义时,unique_ptr比shared_ptr更合适,因为资源只能有一个unique_ptr.
shared_ptr - 同样,如果使用复制语义,shared_ptr似乎还有很长的路要走.可以有多个对象副本,因此拥有一个指向资源的共享指针对我来说很有意义.但是,unique_ptr通常优先于shared_ptr,因此如果可以,请避免使用此选项.
但:
我知道C++ 11从这个链接中移动了语义: 现代C++ Style的元素
但它没有介绍如何使用移动语义返回向量.这该怎么做?
考虑以下功能:
vector<int> get_vector()
{
vector<int> xs;
// Do some stuff to fill the vector with numbers...
return xs;
}
Run Code Online (Sandbox Code Playgroud)
写下面的内容是否有意义?主要目标是避免在返回时复制矢量.
vector<int>&& get_vector()
{
vector<int> xs;
// Do some stuff to fill the vector with numbers...
return std::move(xs);
}
Run Code Online (Sandbox Code Playgroud)
除了避免复制之外,还有其他语义差异吗?
从C++ 11开始,我们就有了移动语义.在下面的示例中,将使用move-constructor(或复制elision),而不是像C++ 98中那样使用copy-constructor,而无需任何额外的工作.
std::string f()
{
std::string res;
...
return res; // <- move is used here instead of copy
}
Run Code Online (Sandbox Code Playgroud)
但是这个案子怎么样?
std::string f()
{
std::optional<std::string> res;
...
return *res; // <-- will the std::string value be moved??
}
Run Code Online (Sandbox Code Playgroud)
或者一个人必须写这样的东西?
std::string f()
{
std::optional<std::string> res;
...
return *std::move(res);
}
Run Code Online (Sandbox Code Playgroud) 我有一个类,其const vector成员拥有对某些对象的唯一指针.构造时,sequence对象应该窃取传递给构造函数的唯一指针向量的所有权,以便序列对象现在是向量参数中唯一指针所拥有的对象的所有者.
class sequence
{
const std::vector< std::unique_ptr< statement > > m_statements;
sequence( std::vector< std::unique_ptr< statement > > & statements );
};
Run Code Online (Sandbox Code Playgroud)
我第一次尝试实现构造函数时,执行了以下操作:
sequence::sequence( vector< unique_ptr< statement > > & statements )
m_statements( statements )
{
}
Run Code Online (Sandbox Code Playgroud)
但是当然这不能编译,因为一个人不能复制构造一个unique_ptr因此不能复制构造一个vector.
C++不允许在构造函数体中初始化const成员(就像Java对最终成员一样),但仅在初始化列表中.因此,一种可能的解决方案可以是删除const修饰符,m_statement并使用循环将内容从构造函数的主体中的一个向量移动到另一个向量.
但我想保留这个const修饰符.
所以我提出了另一种似乎可以编译的解决方案,但是因为我是C++ 11的新手,所以我不确定它是什么样的.我的想法是将上面描述的循环嵌入到lambda函数中,这样我就可以m_statement使用循环在初始化列表中初始化并仍然保持const修改符m_statement.
sequence::sequence( vector< unique_ptr< const statement > > & statements ) :
m_statements(([ & statements ] {
vector< unique_ptr< …Run Code Online (Sandbox Code Playgroud) 我一直在密切关注的建议从来没有写std::movereturn语句中,例如.例如,除了有一些边缘情况.
我相信以下是另一个std::move可能值得的简单例子- 我错过了什么吗?但我不确定为什么,并且将来会改变C++?
#include <iostream>
struct A
{
};
struct C
{
};
struct B
{
B(const A&, const C&) { std::cout << "B was copied\n"; }
B(A&&, C&&) { std::cout << "B was moved\n"; }
};
B f()
{
A a;
C c;
//return {a, c}; // Gives "B was copied"
return {std::move(a), std::move(c)}; // Gives "B was moved"
}
int main() {
f();
return 0;
}
Run Code Online (Sandbox Code Playgroud) 有很多文章讨论价值语义与参考语义,也许更多的文章试图解释移动语义.但是,没有人谈论过值语义和移动语义之间的联系.它们是正交概念吗?
注意:这个问题不是关于比较值语义和移动语义,因为很明显这两个概念不是"可比的".这个问题是关于他们是如何联系的,特别是(如@StoryTeller所说),讨论(如何):
移动语义有助于更多地使用值类型.
尝试使用不同的文件名创建一些gzip存档我写下以下代码片段.
#include <iostream>
#include <utility>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/filter/gzip.hpp>
boost::iostreams::filtering_ostream&& makeGZipStream(const std::string& archiveName,
const std::string& fileName)
{
boost::iostreams::filtering_ostream theGzipStream;
boost::iostreams::gzip_params theGzipParams;
theGzipParams.file_name = fileName;
theGzipStream.push(boost::iostreams::gzip_compressor{theGzipParams});
theGzipStream.push(boost::iostreams::file_sink{archiveName});
return std::move(theGzipStream);
}
int main()
{
boost::iostreams::filtering_ostream&& theGzipStream = makeGZipStream("archive.gz", "file");
theGzipStream << "This is a test..." << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这(我们可以预期)会产生核心转储,因为makeGZipStream我们尝试通过(rvalue-)引用返回本地堆栈分配的变量.但在这种情况下,副本不是一个选项,因为它boost::iostreams::filtering_ostream是不可复制的.
std::unique_ptr"按值"(由于copy-elision,这个移动甚至不应该出现在C++ 17中),为什么在这种情况下不可能呢?unique_ptr(不那么漂亮)使用的编译器很老了g++ (GCC) 4.9.3.
请考虑以下代码:
struct Foo {
std::string s;
Foo(std::string s_) : s(s_) { }
};
Foo* f(std::string s)
{
return new Foo(s);
}
Run Code Online (Sandbox Code Playgroud)
在那里f()可以用左值或右值调用std::string,或与char const*造成临时(但这是一样的右值我恢复).例如:
int main()
{
f("test");
f(std::string());
std::string s("test");
f(std::move(s));
std::string s2("test");
f(s2); // This MUST cause one copy.
}
Run Code Online (Sandbox Code Playgroud)
只有在最后一种情况下才需要一份副本.在所有其他情况下,我希望根本不会复制,并且std::string只构造一次(在分配中Foo).
这可能吗?如果是这样,签名会如何f()?
编辑:
使用Tracked from cwds我创建了一个小测试程序,其中有一个string类在构造,移动等时打印.对于main()函数,请看这个blob.
上面的程序给出了以下输出:
NOTICE : Calling f("test")... <unfinished>
TRACKED : string0*
TRACKED : string1*(string0)
TRACKED : string2*(string1) …Run Code Online (Sandbox Code Playgroud) 下面是我使用删除的副本构造函数和副本分配运算符定义的类。这是唯一必须做的假设。
class MyClass
{
public:
explicit MyClass(int i) : i(i) {}
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
MyClass(MyClass&& other) :
i(std::move(other.i))
{}
MyClass& operator=(MyClass&& other) {
i = std::move(other.i);
return *this;
}
private:
int i;
};
Run Code Online (Sandbox Code Playgroud)
然后的目标是在编译时将我的类添加到std :: vector。
int main()
{
std::vector<MyClass> v{MyClass{0}, MyClass{1}, MyClass{2}};
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我的编译器告诉我STL需要使用已删除的副本构造函数,MyClass::MyClass(const MyClass&)但是有什么解决办法吗?
我已经知道在运行时添加值的一种可能方法,但是我认为以下方法是一个较差的解决方案,因为我丢失了编译时间检查。
int main()
{
std::vector<MyClass> v;
v.emplace_back(MyClass{0});
v.emplace_back(MyClass{1});
v.emplace_back(MyClass{2});
return 0;
}
Run Code Online (Sandbox Code Playgroud) c++ ×10
move-semantics ×10
c++11 ×7
c++17 ×3
copy-elision ×2
const ×1
noncopyable ×1
pointers ×1
rvo ×1
stdstring ×1
unique-ptr ×1
vector ×1