C ++:如何防止通过指向其基础子对象的指针修改派生对象?

时间:2019-02-22 16:59:43

标签: c++ inheritance const implicit-conversion type-safety

以下简化(但仍可编译)的示例说明了可能的切片分配方案。

#include <string>

struct Base
{
    // Mutating method. Not a chance of making it virtual.
    template <typename Anything>
    Base& operator=(const Anything& x)
    {
        m_int = x.AsInteger();
        return *this;
    }

    int AsInteger() const
    {
        return m_int;
    }

    int  m_int;
};

struct Derived : public Base
{
    template <typename Anything>
    Derived& operator=(const Anything& x)
    {
        m_text = x.AsString();
        Base::operator=(x);
        return *this;
    }
    const std::string& AsString() const
    {
        return m_text;
    }

    // Invariant: Derived::m_text matches Base::m_x.
    std::string   m_text;
};

void ExamineBase(const Base* b)
{
    b->AsInteger();
}

void ExamineBase(const Base& b)
{
    b.AsInteger();
}

void InitBase(Base* b)
{
    *b = Base();
}

void InitBase(Base& b)
{
    b = Base();
}


int main()
{
    Base           b;
    InitBase(b);          // <----- (1)
    InitBase(&b);         // <----- (2)

    Derived        d;
    Derived&       ref = d;
    Derived*       ptr = &d;

    ExamineBase(ref);     // <----- (3)
    ExamineBase(ptr);     // <----- (4)

    InitBase(ref);        // <----- (5)
    InitBase(ptr);        // <----- (6)

    return 0;
}

第(1),(2),(3)和(4)行是好的。

第(5)行和第(6)行表现出一个问题:它们仅更改完整对象中的基础子对象,显然破坏了Base :: m_int和Derived :: m_text之间的连贯性。

我有兴趣阻止这种切片修改的发生,但要保持第(1),(2),(3)和(4)行的有效性。

所以问题是:

a)是否有任何技巧可以阻止通过指向派生类的指针来调用基类的非常量成员函数?

b)是否有任何技巧可以阻止从Derived*Base*的标准隐式转换,但仍然允许从Derived*const Base*的转换?

1 个答案:

答案 0 :(得分:2)

免责声明:我正在回答询问的问题,但是如果您想知道如何实现这一目标,那么很有可能是您的设计出了问题。

简短的回答:这不能通过公共继承来完成。公共继承的全部要点是,无论上下文如何,都可以使用对Derived对象的引用或指针作为对Base对象的引用或指针。

因此,执行此操作的方法将是遍历私有继承或成员变量,并仅通过返回Base引用或指针的访问器来公开const成员:

#include <string>
struct Base
{
    // Mutating method. Not a chance of making it virtual.
    template <typename Anything>
    Base& operator=(const Anything& x)
    {
        m_int = x.AsInteger();
        return *this;
    }

    int AsInteger() const
    {
        return m_int;
    }

    int  m_int;
};

struct Derived : private Base
{
    template <typename Anything>
    Derived& operator=(const Anything& x)
    {
        m_text = x.AsString();
        Base::operator=(x);
        return *this;
    }
    const std::string& AsString() const
    {
        return m_text;
    }

    const Base& base() const {return *this;}

    // Invariant: Derived::m_text matches Base::m_x.
    std::string   m_text;
};

void ExamineBase(const Base* b)
{
    b->AsInteger();
}

void ExamineBase(const Base& b)
{
    b.AsInteger();
}

void InitBase(Base* b)
{
    *b = Base();
}

void InitBase(Base& b)
{
    b = Base();
}


int main()
{
    Base           b;
    InitBase(b);          // <----- (1)
    InitBase(&b);         // <----- (2)

    Derived        d;
    Derived&       ref = d;
    Derived*       ptr = &d;

    ExamineBase(ref.base());     // <----- (3)
    ExamineBase(&ptr->base());     // <----- (4)

    InitBase(ref.base());        // <----- BOOM!
    InitBase(&ptr->base());        // <----- BOOM!

    return 0;
}