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

What is the best way to pass a (temporary?) std::string to a function that uses it to construct an object that takes a copy?

考虑以下代码:

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 from cwds I created a little test program with a string class that prints when it is constructed, moved etc. For the main() function please see this 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~

正如预期的那样,显示大量复制。

使用 max66 的第一个建议,参见 this change 我得到以下输出:

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 回答)。

使用 max66 的第二个建议,虽然我省略了模板,因为我认为这里不需要它?查看this commit中的不同,输出变为:

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

您标记了 C++17,因此您可以使用移动语义(来自 C++11)并在 main() 中使用它。

在我看来,收到副本的签名就可以了。

但是你必须在f()

里面使用std::move()
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)}; }