为啥可以从函数返回“向量”?
Posted
技术标签:
【中文标题】为啥可以从函数返回“向量”?【英文标题】:Why is it OK to return a 'vector' from a function?为什么可以从函数返回“向量”? 【发布时间】:2014-05-04 12:06:49 【问题描述】:请考虑此代码。我已经多次看到这种类型的代码。 words
是一个局部向量。如何从函数中返回它?
我们能保证它不会死吗?
std::vector<std::string> read_file(const std::string& path)
std::ifstream file("E:\\names.txt");
if (!file.is_open())
std::cerr << "Unable to open file" << "\n";
std::exit(-1);
std::vector<string> words;//this vector will be returned
std::string token;
while (std::getline(file, token, ','))
words.push_back(token);
return words;
【问题讨论】:
返回时被复制。 没有人保证......它会死,但在被复制之后。 只有在函数返回引用时才会出现问题:std::vector<std::string>&
@songyuanyao 不会,会搬家的。
@songyuanyao 是的。 C++11 是当前的标准,所以 C++11 就是 C++。
【参考方案1】:
C++11 前:
该函数不会返回局部变量,而是返回它的副本。但是,您的编译器可能会在没有进行实际复制操作的情况下执行优化。
更多详情请参阅this question & answer。
C++11:
函数将移动值。详情请见this answer。
【讨论】:
它将被移动,而不是复制。这是有保证的。 这是否也适用于 C++10? 没有 C++10 这样的东西。 C++03 没有移动语义(但可能已经省略了复制),但 C++ 是 C++11,问题是关于 C++。 C++11 独有的问题有一个单独的标签。我们中的许多人,尤其是大公司的程序员,仍然坚持使用尚未完全支持 C++11 的编译器。我更新了问题,使其对这两个标准都准确。【参考方案2】:我们能保证它不会死吗?
只要没有返回引用,这样做就完全没问题。 words
将被移动到接收结果的变量中。
局部变量将超出范围。在移动(或复制)之后。
【讨论】:
但是对于可能包含 1000 个条目的向量来说,效率高还是有任何性能问题? @zadane 有问题吗?我还提到了 moving 这将避免实际获取返回值的副本(至少在当前标准下可用)。 不,不是真正的问题,但我正在独立地从这个角度寻找答案。我不知道我是否发布了我的问题,恐怕他们会将其标记为重复 :) @zadane “我担心他们会将其标记为重复” 很可能。看看higher voted answer。即使对于较旧的实现,您也不用担心,这些编译器无论如何都会正确优化它们。【参考方案3】:我认为您指的是 C(和 C++)中不允许从函数返回数组(或至少不会按预期工作)的问题 - 这是因为数组返回将(如果您写成简单的形式)返回一个指向堆栈上实际数组的指针,然后在函数返回时立即将其删除。
但在这种情况下,它可以工作,因为std::vector
是一个类,并且类,如结构,可以(并且将)复制到调用者上下文中。 [实际上,大多数编译器将使用称为“返回值优化”的东西来优化这种特定类型的副本,专门引入以避免在从函数返回时复制大对象,但这是一种优化,从程序员的角度来看,它将表现得好像为对象调用了赋值构造函数]
只要你不返回一个指针或对函数返回的东西的引用,你就可以了。
【讨论】:
【参考方案4】:为了更好地理解行为,您可以运行以下代码:
#include <iostream>
class MyClass
public:
MyClass() std::cout << "run constructor MyClass::MyClass()" << std::endl;
~MyClass() std::cout << "run destructor MyClass::~MyClass()" << std::endl;
MyClass(const MyClass& x) std::cout << "run copy constructor MyClass::MyClass(const MyClass&)" << std::endl;
MyClass& operator = (const MyClass& x) std::cout << "run assignation MyClass::operator=(const MyClass&)" << std::endl;
;
MyClass my_function()
std::cout << "run my_function()" << std::endl;
MyClass a;
std::cout << "my_function is going to return a..." << std::endl;
return a;
int main(int argc, char** argv)
MyClass b = my_function();
MyClass c;
c = my_function();
return 0;
输出如下:
run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run constructor MyClass::MyClass()
run my_function()
run constructor MyClass::MyClass()
my_function is going to return a...
run assignation MyClass::operator=(const MyClass&)
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()
run destructor MyClass::~MyClass()
请注意,此示例是在 C++03 上下文中提供的,它可以针对 C++ >= 11 进行改进
【讨论】:
这个例子如果包含一个移动构造函数和一个移动赋值运算符,而不仅仅是复制构造函数和复制赋值运算符,会更完整。 (如果移动功能不存在,将使用复制功能。) @SomeGuy 我同意,但我不使用 C++11。我无法提供我没有的知识。我添加一个注释。随意添加 C++ >= 11 的答案。:-)【参考方案5】:我不同意并且不推荐返回vector
:
vector <double> vectorial(vector <double> a, vector <double> b)
vector <double> c a[1] * b[2] - b[1] * a[2], -a[0] * b[2] + b[0] * a[2], a[0] * b[1] - b[0] * a[1] ;
return c;
这要快得多:
void vectorial(vector <double> a, vector <double> b, vector <double> &c)
c[0] = a[1] * b[2] - b[1] * a[2]; c[1] = -a[0] * b[2] + b[0] * a[2]; c[2] = a[0] * b[1] - b[0] * a[1];
我在 Visual Studio 2017 上进行了测试,在发布模式下得到以下结果:
8.01 参考文献 5.09 MOPs 返回向量
在调试模式下,情况更糟:
0.053 MOPS 参考 返回向量 0.034 MOPs
【讨论】:
【参考方案6】:这实际上是设计的失败。您不应该将返回值用于任何不是相对微不足道的东西的原始值。
理想的解决方案应该通过返回参数来实现,该参数决定引用/指针并正确使用“const\'y\'ness”作为描述符。
除此之外,您应该意识到 C 和 C++ 中数组上的标签实际上是一个指针,它的订阅实际上是一个偏移量或加法符号。
因此返回 foo[offset] 的标签或 ptr array_ptr === 数组标签实际上是在内存指针位置 foo + 类型返回类型的偏移量处返回元素。
【讨论】:
.......什么。很明显,您没有资格提出诸如“设计失败”之类的指责。事实上,RVO 和移动操作对值语义的提升是现代 C++ 风格的 主要成功之一。但是您似乎一直在思考原始数组和指针,所以我不希望您掌握这一点。以上是关于为啥可以从函数返回“向量”?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在向量类中实现 operator= 时返回 const 引用