copy-/move-constructors的奇怪行为以及如何返回大对象?
Strange behavior of copy-/move-constructors & how to return large objects?
最近,在缺席一段时间后,我再次尝试使用C++11。在阅读了互联网上的许多文章后,我现在完全不知道从工厂函数(基本上是从数据库中进行数据分析)返回大型对象的最有效方法是什么。
我已经成为unique_ptr的粉丝,但我在几篇文章中读到,由于有了新的move构造函数,现在完全可以通过值返回一个大向量,比如,并且由于这些新的语义,应该像复制一个指针一样快。
为了尝试这一点,我编写了一个小测试程序,在各种构造函数中都有输出:
#include <iostream>
#include <memory>
using namespace std;
class C {
public:
C( string n ) : _name{n} { cout << "Constructing a C named '" << _name << "'n"; };
C() : _name( "EMPTY" ) { cout << "Default-constructing a C named '" << _name << "'n"; } ; // default-ctor
C( const C& c ) : _name{c._name} {
_name += " [copied]";
cout << "Copy-constructing a C named '" << _name << "'n";
};
C( C&& c )
: _name{c._name} {
_name += " [moved]";
cout << "Move-constructing a C named '" << _name << "'n";
};
~C() { cout << "Destructing a C named '" << _name << "'n"; };
string getName() { return _name; };
private:
string _name;
};
并用测试
C fooVal() {
cout << "In fooValn";
string str = "value return";
C c(str);
return c;
}
C& fooRef() {
cout << "In fooRefn";
string str = "reference return";
C* pC = new C( str );
return *pC;
}
C* fooPtr() {
cout << "In fooPtrn";
string str = "classical pointer return";
C* pC = new C( str );
return pC;
}
unique_ptr<C> fooUPtr() {
cout << "In fooUPtrn";
string str = "unique_ptr return";
return unique_ptr<C>(new C(str));
}
shared_ptr<C> fooSPtr() {
cout << "In fooSPtrn";
string str = "shared_ptr return";
return shared_ptr<C>(new C(str));
}
// IMPORTANT: THIS NEEDS TO BE COMPILED WITH FLAG -fno-elide-constructors
int main(int argc, const char * argv[])
{
C cv(fooVal());
cout << "cv constructedn";
C& cr = fooRef();
cout << "cr constructedn";
C* pC = fooPtr();
cout << "*pC constructedn";
unique_ptr<C> upC = fooUPtr();
cout << "*upC constructedn";
shared_ptr<C> spC = fooSPtr();
cout << "*spC constructedn";
cout << "Alive: " << cv.getName() << ", " << cr.getName() << ", " << pC->getName() << ", " << upC->getName() << ".n";
}
现在,如果我按原样编译它,编译器会优化("elides")各种构造函数调用,我会得到输出:
In fooVal
Constructing a C named 'value return'
cv constructed
In fooRef
Constructing a C named 'reference return'
cr constructed
In fooPtr
Constructing a C named 'classical pointer return'
*pC constructed
In fooUPtr
Constructing a C named 'unique_ptr return'
*upC constructed
In fooSPtr
Constructing a C named 'shared_ptr return'
*spC constructed
Alive: value return, reference return, classical pointer return, unique_ptr return.
Destructing a C named 'shared_ptr return'
Destructing a C named 'unique_ptr return'
Destructing a C named 'value return'
好吧,但是你可以看到有多少复制被优化掉了。为了了解"指定"行为是什么,我使用标志-fno-elide构造函数编译了它(我使用的是Apple LLVM 4.2版(clang-425.0.28))
In fooVal
Constructing a C named 'value return'
Destructing a C named 'value return'
Move-constructing a C named ' [moved]'
Destructing a C named ''
cv constructed
In fooRef
Constructing a C named 'reference return'
cr constructed
In fooPtr
Constructing a C named 'classical pointer return'
*pC constructed
In fooUPtr
Constructing a C named 'unique_ptr return'
*upC constructed
In fooSPtr
Constructing a C named 'shared_ptr return'
*spC constructed
Alive: [moved], reference return, classical pointer return, unique_ptr return.
Destructing a C named 'shared_ptr return'
Destructing a C named 'unique_ptr return'
Destructing a C named ' [moved]'
所以,很明显,返回值的对象发生了一些可疑的事情。显然,这不仅仅是一个小问题,因为我本以为-fno-elide构造函数不会改变语义,只会改变所涉及的构造函数数量。
因此我问:
- 发生了什么事?为什么value对象会"丢失"其字符串参数?在哪里
- 看起来价值回报有问题,而其他回报运作良好。那么,为什么现在人们建议我们按价值回归,"系统会照顾剩下的"呢
- 返回大型对象的好方法是什么
- 我是不是在一个我没有看到的地方犯了一个错误
谢谢!
看起来是这个clang bug:http://llvm.org/bugs/show_bug.cgi?id=12208其在简化的同时是与字符串串接相关的并且显然仍然不是固定的。
老实说,我不认为-fno-elide构造函数会产生有效的程序。
因为它会立即在我的系统上崩溃,而valgrind很快指出了主要错误:
==6098== Memcheck, a memory error detector
==6098== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==6098== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==6098== Command: ./test
==6098==
In fooVal
Constructing a C named 'value return'
Destructing a C named 'value return'
==6098== Use of uninitialised value of size 8
==6098== at 0x4EEF83B: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17)
==6098== by 0x40297C: C::C(C&&) (test.cpp:19)
==6098== by 0x401C0C: C::C(C&&) (test.cpp:22)
==6098== by 0x40165D: main (test.cpp:69)
==6098==
==6098== Conditional jump or move depends on uninitialised value(s)
==6098== at 0x4EEF84D: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.17)
==6098== by 0x40297C: C::C(C&&) (test.cpp:19)
==6098== by 0x401C0C: C::C(C&&) (test.cpp:22)
==6098== by 0x40165D: main (test.cpp:69)
这是使用
Ubuntu clang version 3.2-9 (tags/RELEASE_32/final) (based on LLVM 3.2)
Target: x86_64-pc-linux-gnu
Thread model: posix
这可能是编译器错误,也可能是关于-fno-elide-constructors
的"阅读文档"的情况。我还没查。
这就是我编写fooVal
:的方式
C fooVal()
{
cout << "In fooValn";
return C("value return");
}
这就是我写C::C(C&&)
的方式——尽管在这种情况下应该自动生成。。。
C( C&& c ) : _name{std::move(c._name)} // note the "std::move"
{
_name += " [moved]";
cout << "Move-constructing a C named '" << _name << "'n";
};
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- 什么时候在C++中返回常量引用是个好主意
- 你能重载对象变量名本身返回的内容吗
- 为什么 Serial.println(<char[]>);返回随机字符?
- C++映射:具有自定义类的运算符[]不起作用(总是返回0)
- 如何获取std::result_of函数的返回类型
- QueryWorkingSet总是返回false
- (C++)分析树以计算返回错误值的简单算术表达式
- 访问者访问变体并返回不同类型时出错
- 如何返回一个类的两个对象相加的结果
- OpenInventor从9.8升级到10.4.2后,GLSL纹理返回零
- lower_bound()返回最后一个元素
- 如何将 cv::mat 对象从 python 模块传递到 c++ 函数并返回返回的 cv::mat 类型为对象?
- 奇怪的行为当我们在返回返回返回时,奇怪的行为
- 返回返回基本或派生CLSS指针的功能值
- 用于检查使用大数字 C++ 时返回返回 0 的最大素因数的函数
- 函数不会返回返回c 的内容
- 使用 system() 执行命令返回返回值的 256 倍 ( << 8).这有什么意义
- C++函数,不返回返回数据
- 隐式返回返回类型变量