我在 std::string 周围使用包装类,但是初始化/构造它的最简单/最干净/最完整的方法是什么。我至少需要 3 种方式
- 从字符串文本
- 从 std::字符串右值,避免复制!?
- 从string_view和字符串(是的,复制)
天真的程序员只想将任何构造自动委托给 std::string,但这不是一个功能。
struct SimpleString
{
SimpleString() = default;
template<typename T>
SimpleString( T t ) : Text( t ) { } // <==== experimental
// Alternative: are these OK
SimpleString( const char* text ) : Text( text ) { }
SimpleString( std::string&& text ) : Text( text ) { }
SimpleString( const std::string_view text ) : Text( text ) { }
std::string Text;
};
先发制人:是的,我想要它,我需要它。用例:调用一个泛型函数,其中 SimpleString 的处理方式与 std::string 不同。
关于从 std::string 继承的注意事项:这可能是一个坏主意,因为隐式转换会在第一时间发生。
如果您的主要目标是字符串转换构造函数,则无需经历任何巨大的障碍。最简单和最好的方法是只接受 API 上的std::string
按值并将其std::move
到位。
class SimpleString
{
public:
SimpleString() = default;
SimpleString(std::string text) : Text(std::move(text)) { }
// ... other constructors / assignment ...
private:
std::string Text;
};
这样做,我们可以通过以下方式利用std::string
已经可以构造的事实:
- C 字符串/文字,
std::string_view
,- 其他
std::string
对象(左值或右值),以及 - 任何可能已可转换为
std::string
的用户定义类型
这样做还使用户现在可以选择通过移动其当前std::string
对象来构造SimpleString
,还是允许SimpleString
显式复制它。这为调用方提供了更大的灵活性。
这很好用,因为无论如何您已经拥有此std::string
,因此按值接受它并std::move
对象是"坐"此值的简单方法。移动std::string
相当便宜,相当于指针、大小和分配器的一些分配 - 因此这会产生可以忽略不计的性能开销,同时也是一种简单且可维护的方法。
现代C++和移动语义使得接受这些类型变得如此容易。你绝对可以去重载所有N种构造它的方式(const char*
,std::string_view
,T
可转换为字符串等);但是,按原样接受类型并移动它会简单得多。这是最灵活的方法,同时也保持其简单性和可维护性。
以下是一些使用模板与使用std::string
+std::move
进行比较的基准。一般来说,保持简单要好得多。你的同事和未来的维护者会感谢你(即使未来的维护者是你,一年后)。
std::string
有很多构造函数,必须在类中重新实现所有这些构造函数会很麻烦。这就是为什么你最适合转发给std::string
的构造函数,如下所示:
#include <utility>
struct SimpleString
{
template <typename... Args>
SimpleString(Args &&... args) : Text(std::forward<Args>(args)...)
{
}
std::string Text;
};
为了避免与默认和复制/移动构造函数发生冲突,我们需要使用 SFINAE:
template <typename... Args>
constexpr bool is_empty_pack_v = sizeof...(Args) == 0;
template <typename T, typename... Args>
constexpr bool is_copy_or_move_pack_v = sizeof...(Args) == 1 &&
(... && std::is_same_v<T, std::remove_reference_t<Args>>);
struct SimpleString
{
SimpleString() = default;
SimpleString(const SimpleString &) = default;
SimpleString(SimpleString &&) = default;
template <typename... Args, std::enable_if_t<
not is_empty_pack_v<Args...> &&
not is_copy_or_move_pack_v<SimpleString, Args...> &&
std::is_constructible<std::string, Args...>, int> = 0>
SimpleString(Args &&... args) : Text(std::forward<Args>(args)...)
{
}
std::string Text;
};