PIMPL类的setter应该是const成员函数吗?

时间:2018-03-27 22:12:14

标签: c++ pimpl-idiom

我经常使用“私有实现指针”类。这些类的setter方法在技术上可以是const成员函数,例如:

$advixe-cl -report survey <project-dir>

这是可能的,因为编译器只强制执行按位const,而不是逻辑const,所以上面的代码应该编译得很好。

我认为那些setter方法应该 NOT 是const成员函数,让调用者知道对象实际被修改(逻辑修改,不按位修改,因为指向实现)。

我的问题是:

是否有充分的理由让这些setter方法成为const成员函数?

有效的C ++建议(在第3项中)尽可能使用const,但我不认为这适用于我的例子。

2 个答案:

答案 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()还会检查接口的常量是否与实现相同,覆盖了另一种情况。