将(临时?)std :: string传递给使用它来构造一个带有副本的对象的函数的最佳方法是什么?

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()一个字符串添加一个重载,那么前两个调用变得不明确:(.

max*_*x66 7

您标记了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)