C++ “move from” container
在C++ 11中,当我们想要移动(破坏性拷贝)值到容器时,可以使用EDCOX1×0来获得效率提升:
1 2
| SomeExpensiveType x = /* ... */;
vec.push_back(std::move(x)); |
但我找不到任何相反的方向。我的意思是这样的:
1 2
| SomeExpensiveType x = vec.back(); // copy!
vec.pop_back(); // argh |
这在适配器(如stack上)上更为频繁(copy pop)。是否可能存在这样的情况:
1
| SomeExpensiveType x = vec.move_back(); // move and pop |
为了避免复制?这已经存在了吗?我在3000号公路上找不到类似的东西。
我有一种感觉,我错过了一些显而易见的痛苦的东西(比如它的不必要性),所以我准备好了"ru dum"。:3
- move方法对超出范围的变量有什么影响?例如,如果我创建一个对象,将其添加到成员容器中,那么该对象就超出了范围…由于没有复制,成员容器中的对象是否仍被定义?
- 是的,它被移到集装箱里了。如果你不确定引用是如何工作的,你会想用谷歌搜索它们的值。
- 太好了,谢谢…我得再深入研究一下这个问题。从表面上看,这似乎会引起一些问题。
我可能完全错了,但这不是你想要的
1
| SomeExpensiveType x = std::move( vec.back() ); vec.pop_back(); |
假设某个昂贵类型具有移动构造函数。(你的情况显然是这样)
- 我想到了,但我对从容器里搬东西很小心。现在我想得更多了,这似乎是完全合法的…:
- 嗯。我不确定那是对的。当然vec.back()必须具有移动语义,即返回&;&;
- 注意,我已经考虑过了,你可能是对的。
- @可怕的是:back返回一个引用,使用std::move将把它变成一个右值引用。
- @GMAN:不用担心,move是类提供的服务,不是容器。容器中的对象仍然保证有效。
- @波塔特:这也是我得出的结论。出于某种原因,我想把它搬出去会使容器失效。但正如你所说,这显然是一个独立于容器的操作。谢谢,伙计们。
- 从技术上讲,我认为这是不允许的,但它可能至少会正常工作。问题是容器中的任何东西都必须是有效的对象,但是从move()到pop_back(),容器中的内容不再真正有效——它基本上是僵尸对象。在一些情况下,类中的大多数对象都很昂贵,但是可以便宜地创建一个——在这种情况下,您可以(有时)创建一个便宜的对象,将其与容器中的对象交换,然后弹出便宜的对象。不过,这并不理想。
- @杰瑞:不,一个"从"对象仍然有效。它存在,只是通常重置为一个非常简单的状态。它的生存期尚未到期,它的析构函数尚未被调用,但将被调用。就语言而言,对象是完全有效的。
- @贾夫:我怀疑这是意图,但我不确定是否需要,至少在目前的标准草案中是这样。我能看到的最接近的是(表33):"[注:建造后RV的值没有要求。-结束注]"这意味着RV有一个值,你可以理解为RV必须仍然是一个有效的对象-但同样,1)它充其量是一个相当弱的含义,2)它只是一个注释。
- @杰瑞:这就是我的思考过程。(我觉得不是一个人在上面更好:p)但是僵尸状态是有效的状态,我认为标准所说的"没有对右值的要求"意味着没有特殊的要求;我们仍然必须确保右值是一个无用但可用的对象。
- 哇。"建造后对RV的价值没有任何要求"似乎是一种非常误导性的方式来描述将在其上运行一个析构函数的东西。
- @杰瑞@jalf@daniel:应该注意的是,标准所说的已经改变了。从FCD中,表34和36(从33移到33,不相关)说:"注意:RV仍然是一个有效的对象。它的状态是未指定的结束注释]"所以它是明确的:移动的对象需要能够安全地销毁,但没有指定其他内容。很高兴他们澄清了这一点。
- @jerry@gman:moved-from对象应该是有效的,因为它至少是可销毁的。另外,如果类型支持赋值,那么它也应该可以在"moved from"状态下赋值。嗯,这是最低限度。任何额外的保证都取决于类设计器。我会考虑任何一门课,但这门课不一定会被打破。
1 2 3 4 5 6 7 8
| template<class C>
auto pop_back(C& c) -> typename std::decay<decltype(c.back())>::type
{
auto value (std::move(c.back()));
c.pop_back();
return value; // also uses move semantics, implicitly
// RVO still applies to reduce the two moves to one
} |
- 我发现std::decay比std::remove_reference更合适。例如,它将"const myclass&;"转换为"myclass"(删除const)。
- @赛利比茨:你说得对,这更合适;谢谢!
- 我不是百分之百确定,但在->之后,typename可能不见了。
- 的确如此。为什么这看起来要简单得多?
- 在某些上下文中,类型名是不必要的(例如,在基类列表中)。因为只有一个类型可以跟在箭头后面,所以typename是相当多余的。我只是不确定这个上下文是否需要类型名。
- @赛利比策:我对草案和当前GCC的解释同意:这里是必需的。
为了完整性(并且任何人在没有C++ 1x编译器的情况下绊倒这个问题),一个已经存在的替代方案:
1 2 3
| SomeExpensiveType x;
std::swap(x, vec.back());
vec.pop_back(); |
它只要求元素类型存在std::swap的专门化。
- 除非不要显式使用std::swap(因为它不能针对许多类型进行适当的专门化);使用带有非限定swap调用的using声明。
- @或者用boost::swap得到同样的东西。
- 我宁愿只打一行字,也不愿担心谁会抱怨Boost,这是不可避免的。:)
- 没有C++ 1x。
- 除非你意识到它已经2010了,标准还没有完成,这意味着C++ 0中的"原来"的意思已经过时了。我还是把它称为C++0X,就像其他人一样,但是你可以用任何方式争论。
- 有时,如果我觉得不乐观,我会加上C2+2X。
通常对于昂贵的类型,我认为您应该将包装类或智能指针推送到容器中。这样就避免了昂贵的副本,而只做智能指针或包装类的廉价副本。如果你想要哈哈,你也可以使用原始指针。
1 2 3 4 5 6 7 8 9 10 11
| class ExpensiveWrapper
{
public:
ExpensiveWrapper(ExpensiveClass* in) { mPtr = in; }
// copy constructors here....
private:
ExpensiveWrapper* mPtr;
}; |
- move语义的一个要点是去掉这个方法。容器将只保留它们的内容,而不复制它们。