我经常使用“私有实现指针”类。这些类的setter方法在技术上可以是const成员函数,例如:
$advixe-cl -report survey <project-dir>
这是可能的,因为编译器只强制执行按位const,而不是逻辑const,所以上面的代码应该编译得很好。
我认为那些setter方法应该 NOT 是const成员函数,让调用者知道对象实际被修改(逻辑修改,不按位修改,因为指向实现)。
我的问题是:
是否有充分的理由让这些setter方法成为const成员函数?
有效的C ++建议(在第3项中)尽可能使用const,但我不认为这适用于我的例子。
答案 0 :(得分:3)
“尽可能使用const
”过于简单化。这是一个很好的起点,但不是绝对的规则。
正如您所说,在pimpl习语中,应用规则会为我们提供const
个设置者。但强烈的反驳是,这打破了封装。您的界面现在反映了实施选择。想象一下你重构不使用pimpl。您班级的用户不应该关心完全内部决策,但现在他们这样做是因为您必须从设置者中删除const
。
只要有私有(对用户)但远程(来自类)状态,就可以生成相同的参数。重构将该状态带入类中将需要逻辑上非const成员函数不被标记为const
。
如果你能想象一个合理的实现选择,要求成员函数不是const
,那么将其标记为const
是合理的。
库基础知识TS中有一个类模板propagate_const
,但这很容易手工编写,这可以帮助你const
- 正确的pimpl习语:
#include <experimental/propagate_const>
#include <memory>
template<class T>
using pimpl = std::experimental::propagate_const<std::unique_ptr<T>>;
struct MyPrivateClass
{
int something = 1;
};
struct MyClass
{
void setSomething(int something) const
{
// error: assignment of member 'MyPrivateClass::something' in read-only object
p->something = something;
}
pimpl<MyPrivateClass> p;
};
另请注意,在另一个答案中,代码示例无法编译:
error: decltype cannot resolve address of overloaded function
is_const<decltype(&A::x)>::value == \
^
note: in expansion of macro 'GET'
void f1() { GET(f1).f1(); } // OK
答案 1 :(得分:0)
通常,您希望模仿接口类中实现类的限定符。
如果你想获得幻想,你使用的是C ++ 11并且你使用了许多pimpl习惯用法,那么你可以确保使用正确的限定符,并且你也可以获得对实现类的适当限定的引用类似的东西:
#include <type_traits>
struct AImpl
{
void f1();
void f2() const;
};
template<typename T>
struct is_const;
template<typename R, typename T, typename... Args>
struct is_const<R (T::*)(Args...) const> : std::true_type {};
template<typename R, typename T, typename... Args>
struct is_const<R (T::*)(Args...)> : std::false_type {};
class A
{
AImpl * p;
template<class T>
typename std::enable_if<!is_const<T>::value, AImpl &>::type get()
{
return *p;
}
template<class T>
typename std::enable_if<is_const<T>::value, const AImpl &>::type get() const
{
return *p;
}
public:
#define GET(x) \
static_assert( \
is_const<decltype(&A::x)>::value == \
is_const<decltype(&AImpl::x)>::value, \
"Interface does not mimic the implementation" \
); \
get<decltype(&AImpl::x)>()
void f1() { GET(f1).f1(); } // OK
void f1() const { GET(f1).f1(); } // Error
void f2() { GET(f2).f2(); } // Error
void f2() const { GET(f2).f2(); } // OK
#undef GET
};
如果成员函数指针T是const,则 get<T>()
返回对实现的const引用;否则,非常数。使用它已经涵盖了一个错误案例,并为您提供了适当的合格参考。
如果你想进一步推进,GET()
还会检查接口的常量是否与实现相同,覆盖了另一种情况。