如果我使用转换来访问受保护的成员,会出现什么问题?

What could go wrong if I use a cast to access a protected member?

以下代码无法编译,因为显然这就是受保护成员的工作方式。 (有一些问题回答了为什么,例如 here)。

class Parent {
protected:
    int value;
};

class Child : public Parent {
public:
    void set_value_of(Parent* x, int value) {
        x->value = value;
    }
};

int main() {
}

例如,GCC 报告

main.cpp: In member function 'void Child::set_value_of(Parent*, int)':
main.cpp:11:16: error: 'int Parent::value' is protected within this context
            x->value = value;
                ^~~~~
main.cpp:5:13: note: declared protected here
        int value;
            ^~~~~

如果将 x 转换为 Child,可能会出现什么问题? 永远不要这样做还是你可以,如果你知道你在做什么

    void set_value_of(Parent* x, int value) {
        static_cast<Child*>(x)->value = value;
    }


编辑 不幸的是,到目前为止所有解决这种滥用的建议都没有帮助我解决我的设计问题,因此我尽量不那么通用并展示我的真实世界案例.

主要目标是 依赖 friend 声明,因为 Node class 不需要修改(即. 不需要添加友元声明), 来实现另一个 subclass 像 Group.

class Node {
protected:
  Node* _parent;
};

class Group : public Node {
protected:
  std::vector<Node*> _nodes;
public:
  void insert(Node* node) {
    if (node->_parent) {
      throw std::runtime_error("node already has a parent");
    }
    _nodes.push_back(node);
    node->_parent = this;
  }
};

这可以通过将 friend class Group 添加到 Node class 来解决,但我正在寻找解决方法。

你可以让它更干净。

void set_value_of(Parent* x, int value) {
    Child* ch = dynamic_cast<Child*>(x);
    if ( ch != nullptr )
    {
       ch->value = value;
    }
}

它很健壮并且符合语言规则。


基于真实代码...

Node,正如发布的那样,是一个设计相当糟糕的 class。它确实应该使 parent_ 成为 public 成员变量或提供获取和设置成员变量的函数。

如果您有选择,请将其更改为:

class Node {
  public:
     Node(Node* p = nullptr) : _parent(p) {}
     Node* getParent() const { return _parent; }
     void setParent(Node* p) { _parent = p; }

  private:
    Node* _parent;
};

What could possibly go wrong if you cast x to Child? Is it a case of never ever do it or you can, if you know what you're doing?

很多东西,这就是为什么你应该使用 dynamic_cast,而不是 static_cast

void set_value_of(Parent* x, int value) {
    Child * child = dynamic_cast<Child*>(x);
    if(child) child->value = value;
}

dynamic_cast 将确保转换将 return nullptr 如果指向的对象实际上不是 Child 对象。

编辑: 根据发生的评论,听起来继承是这些对象的错误设计。如果 Child 应该对它引用的 Parent 对象有直接代理,那么 Child 应该是 Parentfriend,而不应该派生自 Parent 完全没有。你所做的本质上是容易出错的,代表了令人困惑的设计。

class Parent {
    int value;
    friend class Child;
};

class Child {
public:
    void set_value_of(Parent* x, int value) {
        //Is now valid because Child is a friend of Parent
        x->value = value;
    }
};

请注意,考虑到您的实际代码,如果 Group 不打算成为 Node 的特殊类型,我建议进行重构。请参阅此答案底部的注释。


如果您可以修改 Parent,一种方法是提供一个 protected 成员函数作为助手;考虑到 set_value_of() 需要一个实例和一个值,它可能应该是 static 一个。

class Parent {
protected:
    int value;

    // Like this.
    static void set_value_of(Parent* x, int value) { x->value = value; }
};

class Child : public Parent {
public:
    // Provide set_value_of() to the world.
    using Parent::set_value_of;

    // Alternatively...
    //static void set_value_of(Parent* x, int value) {
    //    Parent::set_value_of(x, value);
    //}


    // For example usage.
    int get_value() const { return value; }
};

int main() {
    Child c1, c2;

    c1.set_value_of(&c2, 33);
    c2.set_value_of(&c1, 42);
    Child::set_value_of(&c2, 3);

    std::cout << c1.get_value() << ", " << c2.get_value() << '\n';
}

有了这个,输出将如预期的那样 42, 3


考虑到你的实际代码,这个还是可以实现的。问题是 Node 没有为外部影响提供安全修改它的方法,这通过扩展意味着它没有为 Group 提供安全修改它的方法。 (虽然 Group 确实可以尝试将 Node* 转换为 Group*,但这很可能会出错,并且可能会混淆将来执行维护的任何人。)因此,如果 Node 旨在成为基础 class,并将实际管理各种节点留给子classes,它应该为他们提供一种方法来告诉它做什么。

此外,考虑到您要执行的操作,我建议提供另一个辅助函数 has_parent()。这允许 subclasses 轻松检查给定节点是否有父节点,而无需提供对外界的访问。

class Node {
protected:
  Node* _parent;

  static void set_parent(Node* n, Node* parent) {
    n->_parent = parent;
  }

  static bool has_parent(Node* n) {
    return n->_parent != nullptr;
  }
};

class Group : public Node {
protected:
  std::vector<Node*> _nodes;
public:
  void insert(Node* node) {
    if (has_parent(node)) {
      throw std::runtime_error("node already has a parent");
    }
    _nodes.push_back(node);
    set_parent(node, this);
  }
};

注意,但是,除非Group打算成为管理其他节点的特殊节点,否则我不建议将其设为子class 共 Node;相反,如果没有 is-a 关系,Node 应该将 Group 声明为 friend。与一些流行的看法相反,这并没有破坏封装;相反,它 增加了 封装,因为它使 Group 能够管理 Node 而无需提供其他人可以用来进入 Node 的后门,也(任何代码都可以使用访问器和修改器,并且任何以 Node 作为父级的 class 都可以访问 protected 助手,而 Node 将无力阻止它们;相反,friend 声明让 Node 允许 Group 访问它,同时拒绝访问宇宙的其余部分)。

反之,如果Group确实是Node的一个类型,那么Node应该有一个虚析构函数,这样就可以多态使用了。毕竟,Group 无法知道它的哪些节点是其他 Group

int main() {
    Group g;

    g.insert(new Node);  // Valid.
    g.insert(new Group); // Valid.  Group is just a Node in a fancy suit, right?
}