当从函数返回元组时,元组的参数被复制而不是移动
arguments to tuple are copied instead of moved when returning the tuple from a function
我对以下代码有疑问。我的编译器是 MSVC++ 17 Visual studio 版本 15.3,编译器选项为 /std:c++14(相对于 /std:c++latest)运行 释放模式:
struct Bar
{
int a;
std::string b;
Bar() { std::cout << "default\n"; }
Bar(int a, const std::string& b) : a{ a }, b{ b } { std::cout << "direct\n"; }
Bar(int a, std::string&& b) : a{ a }, b{ std::move(b) } { std::cout << "direct move b\n"; }
Bar(const Bar& other) : a{ other.a }, b{ other.b } { std::cout << "const copy\n"; }
Bar(Bar&& other) : a{ std::move(other.a) }, b{ std::move(other.b) } { std::cout << "move\n"; }
Bar& operator=(const Bar& other)
{
a = other.a;
b = other.b;
std::cout << "const assign\n";
return *this;
}
Bar& operator=(Bar&& other)
{
a = std::move(other.a); //would this even be correct?
b = std::move(other.b);
std::cout << "move assign\n";
return *this;
}
};
std::tuple<Bar, Bar> foo()
{
std::string s = "dsdf";
return { { 1, s }, { 5, "asdf" } };
}
int main()
{
Bar a, b;
std::tie(a, b) = foo();
std::cout << a.a << a.b << std::endl;
std::cout << b.a << b.b;
}
输出为:
default
default
direct
direct move b
const copy <-- Why copy? Why not move>
const copy <-- Why copy? Why not move>
move assign
move assign
1dsdf
5asdf
如果我将 return { { 1, s }, { 5, "asdf" } };
更改为 return { Bar{ 1, s }, Bar{ 5, "asdf" } };
,输出将更改为:
default
default
direct
direct move b
move
move
move assign
move assign
1dsdf
5asdf
问题:为什么在这两种情况下都不执行移动?为什么第一种情况调用的是拷贝构造函数?
你的问题最简单的提炼是为什么:
std::tuple<Bar> t{{5, "asdf"}};
打印
direct move b
const copy
但是
std::tuple<Bar> u{Bar{5, "asdf"}};
打印
direct move b
move
要回答这个问题,我们必须确定这两个声明的实际作用。为了做到这一点,我们必须了解调用了 std::tuple
's constructors 中的哪一个。相关的是(每个构造函数的 explicit
ness 和 constexpr
ness 都不相关,所以为了简洁起见,我省略了它们):
tuple( const Types&... args ); // (2)
template< class... UTypes >
tuple( UTypes&&... args ); // (3)
用 Bar{5, "asdf"}
初始化会调用构造函数 (3)
作为更好的匹配((2)
和 (3)
都是可行的,但我们在(3)
),它将从 UTypes
转发到 tuple
。这就是为什么我们最终得到 move
.
但是只用 {5, "asdf"}
初始化,这个构造函数是不可行的,因为 braced-init-lists 没有可以推导的类型。因此,我们的 只有 选项是 (2)
,我们最终得到一个副本。
解决此问题的唯一方法是添加非模板构造函数,这些构造函数采用每个 Types
的右值引用。但是您将需要 2^N-1
这样的构造函数(除了接受所有 const 左值引用的构造函数之外的所有构造函数 - 因为可以推导出那个),因此我们最终得到了一种在所有情况下都有效但不是最理想的设计。但是因为您可以在调用站点上指定您想要的类型,所以这不是一个大缺陷。
我对以下代码有疑问。我的编译器是 MSVC++ 17 Visual studio 版本 15.3,编译器选项为 /std:c++14(相对于 /std:c++latest)运行 释放模式:
struct Bar
{
int a;
std::string b;
Bar() { std::cout << "default\n"; }
Bar(int a, const std::string& b) : a{ a }, b{ b } { std::cout << "direct\n"; }
Bar(int a, std::string&& b) : a{ a }, b{ std::move(b) } { std::cout << "direct move b\n"; }
Bar(const Bar& other) : a{ other.a }, b{ other.b } { std::cout << "const copy\n"; }
Bar(Bar&& other) : a{ std::move(other.a) }, b{ std::move(other.b) } { std::cout << "move\n"; }
Bar& operator=(const Bar& other)
{
a = other.a;
b = other.b;
std::cout << "const assign\n";
return *this;
}
Bar& operator=(Bar&& other)
{
a = std::move(other.a); //would this even be correct?
b = std::move(other.b);
std::cout << "move assign\n";
return *this;
}
};
std::tuple<Bar, Bar> foo()
{
std::string s = "dsdf";
return { { 1, s }, { 5, "asdf" } };
}
int main()
{
Bar a, b;
std::tie(a, b) = foo();
std::cout << a.a << a.b << std::endl;
std::cout << b.a << b.b;
}
输出为:
default
default
direct
direct move b
const copy <-- Why copy? Why not move>
const copy <-- Why copy? Why not move>
move assign
move assign
1dsdf
5asdf
如果我将 return { { 1, s }, { 5, "asdf" } };
更改为 return { Bar{ 1, s }, Bar{ 5, "asdf" } };
,输出将更改为:
default
default
direct
direct move b
move
move
move assign
move assign
1dsdf
5asdf
问题:为什么在这两种情况下都不执行移动?为什么第一种情况调用的是拷贝构造函数?
你的问题最简单的提炼是为什么:
std::tuple<Bar> t{{5, "asdf"}};
打印
direct move b
const copy
但是
std::tuple<Bar> u{Bar{5, "asdf"}};
打印
direct move b
move
要回答这个问题,我们必须确定这两个声明的实际作用。为了做到这一点,我们必须了解调用了 std::tuple
's constructors 中的哪一个。相关的是(每个构造函数的 explicit
ness 和 constexpr
ness 都不相关,所以为了简洁起见,我省略了它们):
tuple( const Types&... args ); // (2)
template< class... UTypes >
tuple( UTypes&&... args ); // (3)
用 Bar{5, "asdf"}
初始化会调用构造函数 (3)
作为更好的匹配((2)
和 (3)
都是可行的,但我们在(3)
),它将从 UTypes
转发到 tuple
。这就是为什么我们最终得到 move
.
但是只用 {5, "asdf"}
初始化,这个构造函数是不可行的,因为 braced-init-lists 没有可以推导的类型。因此,我们的 只有 选项是 (2)
,我们最终得到一个副本。
解决此问题的唯一方法是添加非模板构造函数,这些构造函数采用每个 Types
的右值引用。但是您将需要 2^N-1
这样的构造函数(除了接受所有 const 左值引用的构造函数之外的所有构造函数 - 因为可以推导出那个),因此我们最终得到了一种在所有情况下都有效但不是最理想的设计。但是因为您可以在调用站点上指定您想要的类型,所以这不是一个大缺陷。