假设我们有一个简单的类
struct MyClass {
const std::string &getString() const & {
return m_string;
}
std::string getString() && {
return std::move(m_string);
}
private:
std::string m_string;
};
正如我们所看到的,m_string
是一个不可变的变量,我们不能修改它
这种结构还保留了这样一个事实,即如果我们将MyClass
的一个实例移动到另一个实例,那么m_string
属性也将被移动。
现在,我们将尝试重构先前的结构:
struct MyClass {
std::string m_string;
};
在这里,我们保留了这样一个事实,即我们可以访问或移动它,但我们失去了";不变性";。。。所以我试着这样写:
struct MyClass {
const std::string m_string;
};
这里我们得到了不变性,然而,当我们移动对象时,我们失去了潜在的优化。。。
那么,在不编写所有getter的情况下,是否可以有类似于第一段代码的行为?
编辑:std::string
只是一个例子,但这个想法必须适用于所有类型的对象
那么,在不编写所有getter的情况下,是否可以有类似于第一段代码的行为?
我想不出任何。
话虽如此,为成员变量编写getter和setter的开销并不是一个很大的负担,我不会花太多时间思考它
然而,有些人认为成员变量的getter和setter并没有为类添加足够的保护,甚至不必担心它们。如果你认同这种思路,你就可以完全摆脱getter和setter。
我使用了";没有getter和setter";原则的数据容器足够的时间,我发现它在许多用例中是自然的。
您可以使用模板包装器类型来实现此行为。您似乎想要一种能很好地处理复制和移动构造和赋值的类型,但它只提供对包装对象的const
访问。您只需要一个带有转发构造函数、隐式转换运算符和取消引用运算符的包装器(在隐式转换不起作用时强制转换(:
template<class T>
class immutable
{
public:
template<class ... A>
immutable(A&&... args) : member(std::forward<A>(args)...) {}
public:
operator const T &() const { return member; }
const T & operator*() const { return member; }
const T * operator->() const { return &member; }
private:
T member;
};
这将很好地与编译器生成的复制和移动构造和赋值配合使用。然而,该解决方案并非100%透明。如果上下文允许,包装器将隐式转换为对包装类型的引用:
#include <string>
struct foo
{
immutable<std::string> s;
};
void test(const std::string &) {}
int main()
{
foo f;
test(f.s); // Converts implicitly
}
但在隐式转换不起作用的情况下,它需要一个额外的解引用来强制转换:
int main()
{
foo f;
// std::cout << f.s; // Doesn't work
std::cout << *(f.s); // Dereference instead
// f.s.size(); // Doesn't work
f.s->size(); // Dereference instead
}
有人提议增加.
运算符的重载,这将允许大多数情况按预期工作,而不需要取消引用。但我不确定该提案的现状
解决方案是使用std::shared_ptr<const std::string>
。指向不可变对象的共享指针具有值语义。写时复制可以使用shared_ptr::unique()
实现。参见Sean Parent演示47:46https://youtu.be/QGcVXgEVMJg.
如果您愿意将复制和移除ctor声明为=default
并使用const_cast
欺骗定义移动ctor就好了。
MyClass::Myclass()=default;
MyClass::Myclass(Myclass const&)=default;
MyClass::Myclass(Myclass && m)
: m_string{std::move(const_cast<std::string&>(m.m_string))}{};