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

标签 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 类的小测试程序,该类在构造、 move 等时打印。对于 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~

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

使用 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 左值的传递,因为 我得到了错误:

错误:无法将“string&&”类型的右值引用绑定(bind)到“string”类型的左值

有没有办法解决这个问题,让我得到理想的输出,并且在我尝试传递左值时仍然可以正常工作?请注意,如果我为采用字符串的 f() 添加重载,那么前两个调用将变得不明确 :(。

最佳答案

您标记了 C++17,因此您可以使用 move 语义(来自 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)}; }

关于c++ - 将(临时?) std::string 传递给使用它来构造需要拷贝的对象的函数的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51847318/

相关文章:

c++ - 无法停止来自 python C 扩展的 python 回调

c++ - c++11 中是否有 Boost.Bimap 替代方案?

c++ - 如何在编译时将常量 float 值转换为 4 字节十六进制常量值?

c++ - Visual Studio 2013 不会忽略禁用的警告

c++ - 从 boost asio 客户端发送数据

c++ - 具有未初始化成员的结构的 constexpr 默认构造函数仅在模板化时有效

c++ - if constexpr std::is_same 在 VS 2022 下

c++ - 通过 mapped_type 的 r 值 move 插入映射条目

c++ - 重置 move 对象的常用惯用法是什么?

c++ - vector 增长时如何强制执行 move 语义?