Car*_*ood 1 c++ stdstring move-semantics c++17
请考虑以下代码:
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)
TRACKED : string1~
TRACKED : string0~
NOTICE : <continued> done
TRACKED : string2~
NOTICE : Calling f(string())... <unfinished>
TRACKED : string3*
TRACKED : string4*(string3)
TRACKED : string5*(string4)
TRACKED : string4~
TRACKED : string3~
NOTICE : <continued> done
TRACKED : string5~
NOTICE : Constructing s("test")... <unfinished>
TRACKED : string6*
NOTICE : <continued> done
NOTICE : Calling f(std::move(s))... <unfinished>
TRACKED : string6=>string7*
TRACKED : string8*(string7)
TRACKED : string9*(string8)
TRACKED : string8~
TRACKED : string7~
NOTICE : <continued> done
TRACKED : string9~
NOTICE : Constructing s2("test")... <unfinished>
TRACKED : string10*
NOTICE : <continued> done
NOTICE : Calling f(std::move(s))... <unfinished>
TRACKED : string11*(string10)
TRACKED : string12*(string11)
TRACKED : string13*(string12)
TRACKED : string12~
TRACKED : string11~
NOTICE : <continued> done
TRACKED : string13~
NOTICE : Leaving main()...
TRACKED : string10~
TRACKED : string6~
Run Code Online (Sandbox Code Playgroud)
正如预期的那样,这表明了大量的复制.
使用max66的第一个建议,看到此更改我得到以下输出:
NOTICE : Calling f("test")... <unfinished>
TRACKED : string0*
TRACKED : string0=>string1*
TRACKED : string1=>string2*
TRACKED : string1~
TRACKED : string0~
NOTICE : <continued> done
TRACKED : string2~
NOTICE : Calling f(string())... <unfinished>
TRACKED : string3*
TRACKED : string3=>string4*
TRACKED : string4=>string5*
TRACKED : string4~
TRACKED : string3~
NOTICE : <continued> done
TRACKED : string5~
NOTICE : Constructing s("test")... <unfinished>
TRACKED : string6*
NOTICE : <continued> done
NOTICE : Calling f(std::move(s))... <unfinished>
TRACKED : string6=>string7*
TRACKED : string7=>string8*
TRACKED : string8=>string9*
TRACKED : string8~
TRACKED : string7~
NOTICE : <continued> done
TRACKED : string9~
NOTICE : Constructing s2("test")... <unfinished>
TRACKED : string10*
NOTICE : <continued> done
NOTICE : Calling f(std::move(s))... <unfinished>
TRACKED : string11*(string10)
TRACKED : string11=>string12*
TRACKED : string12=>string13*
TRACKED : string12~
TRACKED : string11~
NOTICE : <continued> done
TRACKED : string13~
NOTICE : Leaving main()...
TRACKED : string10~
TRACKED : string6~
Run Code Online (Sandbox Code Playgroud)
哪个是完美的副本!虽然有很多感动,但这让我觉得我也可以只使用好旧的std::string const&(如果我使用它应该被std::string_view我理解的替换 - 我不应该在这里使用,因为最后我请取得字符串的所有权;根据此答案).
使用max66的第二个建议,虽然我遗漏了模板,因为我觉得这不需要吗?看到此提交中的不同,输出变为:
NOTICE : Calling f("test")... <unfinished>
TRACKED : string0*
TRACKED : string0=>string1*
TRACKED : string0~
NOTICE : <continued> done
TRACKED : string1~
NOTICE : Calling f(string())... <unfinished>
TRACKED : string2*
TRACKED : string2=>string3*
TRACKED : string2~
NOTICE : <continued> done
TRACKED : string3~
NOTICE : Constructing s("test")... <unfinished>
TRACKED : string4*
NOTICE : <continued> done
NOTICE : Calling f(std::move(s))... <unfinished>
TRACKED : string4=>string5*
NOTICE : <continued> done
TRACKED : string5~
NOTICE : Leaving main()...
TRACKED : string4~
Run Code Online (Sandbox Code Playgroud)
如果我不得不评论s2左值的传递,那么它非常接近理想,因为我得到了错误:
error: cannot bind rvalue reference of type ‘string&&’ to lvalue of type ‘string’
有没有办法解决这个问题,所以当我尝试传递一个左值时,我得到理想的输出并仍然有效?请注意,如果我为f()一个字符串添加一个重载,那么前两个调用变得不明确:(.
您标记了C++ 17,因此您可以使用移动语义(来自C++ 11)并在其中使用它main().
在我看来,收到副本的签名是可以的.
但你必须在std::move()里面使用f()
Foo * f (std::string s)
{ return new Foo{std::move(s)}; }
// ...............^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
并在Foo()构造函数内部
Foo (std::string s_) : s{std::move(s_)} { }
// ......................^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
或不必要的副本.
另一种方法是使用模板类型,通用引用和std::forward.
我的意思是
struct Foo
{
std::string s;
template <typename S>
Foo (S && s_) : s{std::forward<std::string>(s_)} { }
};
template <typename S>
Foo * f (S && s)
{ return new Foo{std::forward<S>(s)}; }
Run Code Online (Sandbox Code Playgroud)