考虑以下代码:
struct Foo {
std::string s;
Foo(std::string s_) : s(s_) { }
};
Foo* f(std::string s)
{
return new Foo(s);
}
其中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.
}
仅在最后一种情况下确实需要一份副本。
在其他所有情况下,我都希望完全不进行任何复制,而std::string
仅被构造一次(到分配的Foo
中)。
这可能吗?如果是这样,f()
的签名将如何?
编辑:
使用Tracked中的cwds,创建了一个带有string
类的小测试程序,该类在构造,移动等时进行打印。有关main()函数,请参见此{{ 3}}。
上面的程序按原样提供以下输出:
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~
正如预期的那样,其中显示了很多副本。
使用max66的第一个建议,请参见blob,我得到以下输出:
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~
在复制方面,哪一项是完美的!尽管有很多变化,但是我认为我也可以只使用旧的std::string const&
(如果我使用的是旧的std::string_view
,我应该理解,我理解-我不应该这样做不会在这里使用,因为最后我确实拥有该字符串的所有权;根据this change答案)。
使用max66的第二条建议,尽管我省略了模板,因为我认为这里不需要这样做?在this中查看其他内容,输出变为:
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~
如果不是事实,那真是太接近理想了 我不得不注释掉s2左值的传递,因为 这样我得到错误:
error: cannot bind rvalue reference of type ‘string&&’ to lvalue of type ‘string’
是否有一种方法可以解决此问题,所以当我尝试传递左值时,可以获得理想的输出,并且仍然可以正常工作吗?请注意,如果我为f()
添加了一个字符串重载,那么前两个调用就变得不明确了:(。
答案 0 :(得分:7)
您标记了C ++ 17,因此可以使用移动语义(来自C ++ 11),并在main()
中使用它。
在我看来,接收副本的签名是可以的。
但是您必须在std::move()
内使用f()
Foo * f (std::string s)
{ return new Foo{std::move(s)}; }
// ...............^^^^^^^^^
并在Foo()
构造函数中
Foo (std::string s_) : s{std::move(s_)} { }
// ......................^^^^^^^^^
或进行不必要的复制。
一种替代方法是使用模板类型,通用引用和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)}; }