将(临时?)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)}; }
考虑以下代码:
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)}; }