如果我使用强制转换访问受保护的成员,可能会出现什么问题?
What could go wrong if I use a cast to access a protected member?
下面的代码无法编译,因为显然这就是受保护成员的工作方式。(有一些问题回答了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
应该成为Parent
的friend
,而根本不应该从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;
};
- 警告处理为错误这里有什么问题
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 问题:什么是QAbstractItemView::NoEditTriggers的反面
- 当我尝试添加 2 个大字符串时,我无法弄清楚出了什么问题
- 违反const正确性:我应该现实地期待什么问题
- 这个带有模板<类 Vector 的C++代码片段有什么问题>
- 我的逻辑反转字符串中的元音有什么问题?
- 需要以下代码的帮助,下面的代码有什么问题
- 常量公共成员有什么问题?
- c++无值sort()的问题是什么?
- 以下代码中的函数模板有什么问题?
- 这个返回元素位置的基于循环的函数有什么问题?
- creat_list2功能有什么问题?
- 基本的 c++ 问题:如果我在函数中创建某些内容并返回它会发生什么?
- 我遇到了黑客排名中的问题"TWO STRINGS"的三个测试用例的分段错误。原因是什么?
- 什么是钻石问题?是一系列问题还是特定问题?
- 格式说明符C++有什么问题
- 我应该在 main 函数中写什么来测试我的问题?
- 任何人都可以告诉我我的 C++ 代码出了什么问题?
- 方法问题 - 什么会改变值,什么不会改变?什么是无效的?