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

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

本文关键字:问题 什么 成员 受保护 访问 转换 如果      更新时间:2023-10-16

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

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

例如,海湾合作委员会报告

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类不需要修改(即不需要添加友元声明),以实现另一个子类,如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;
}
};

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

请注意,考虑到您的实际代码,如果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打算成为基类,并将实际管理各种节点留给子类,它应该为子类提供一种告诉它该做什么的方法。

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

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旨在成为管理其他节点的特殊节点,否则我不建议将其作为Node的子类;相反,如果没有is-a关系,Node应该将Group声明为friend。 与某种流行的看法相反,这不会破坏封装;相反,它增加了封装,因为它使Group能够管理Node,而无需提供其他人可以用来进入Node的后门(任何代码都可以使用访问器和突变器,任何将Node作为父类的类都可以访问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?
}

如果你把x投给Child,可能会出错吗?这是一个从来没有做过的情况,或者你可以,如果你知道你在做什么?

很多事情,这就是为什么你应该使用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将确保如果所指向的对象实际上不是Child对象,则强制转换将返回nullptr

编辑:根据发生的评论,听起来继承是这些对象的错误设计。如果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;
}
};

你可以让它更干净。

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

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


基于真实代码...

Node,正如所发布的那样,是一个设计得相当糟糕的类。它确实应该使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;
};