将(临时?)std :: string传递给使用它来构造需要复制的对象的函数的最佳方法是什么?

时间:2018-08-14 18:00:50

标签: c++ c++17 move-semantics stdstring

考虑以下代码:

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

1 个答案:

答案 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)}; }