一个派生类如何使用基类的保护成员?

4

假设一个基类 A 定义了一个受保护的成员。一个派生类 B 使用这个成员。

class A
{
public:
  A(int v) : value(v) { }

protected:
  int value;
};

class B : public A
{
public:
  B(int v) : A(v) { }
  void print() const;
  void compare_and_print(const A& other) const;
};

函数B::print仅获取当前成员的值并将其打印:
void B::print() const
{
  std::cout << "Value: " << value << "\n";
}

另一个成员函数B::compare_and_print,接受一个A实例,检查它们的值并打印两者中的最大值:
void B::compare_and_print(const A& other) const
{
  auto max_value = std::max(value, other.value);
  std::cout << "Max value: " << max_value << "\n";
}

如果other是类B的实例,那么这就没问题了。但是函数希望能够处理任何类型的A实例。不幸的是,这段代码无法编译。下面是clang对此的报告:
test.cpp:27:42: error: 'value' is a protected member of 'A'
  auto max_value = std::max(value, other.value);
                                         ^
test.cpp:9:7: note: can only access this member on an object of type 'B'
  int value;
      ^
1 error generated.

这个限制对我来说听起来有些违反直觉。不过,我不会对C++标准提出异议(我仍然对这个决定背后的理由感兴趣)。
我的问题是,在我的项目中,我真的有这样一种用例:派生类有一个方法,它接受基类的实例,并需要访问所接收对象的受保护成员。
最简单的解决方案,也是我目前实现的方案,是向基类添加一个公共成员函数,该函数返回受保护的成员。这种解决方案并不能完全满足我,因为我想避免以这种方式导出成员。
如何使派生类能够使用基类的受保护成员而不通过API导出成员?

编辑:

我想要的答案是一种设计模式的解释,可以解决这个问题,而不必将受保护的成员暴露给外部代码(其中外部意味着不是定义这些类的框架的一部分的代码)。

可能不存在这样的模式,我承认。


你尝试过使用 A::value 吗?如何从派生类函数中调用父类函数?。因此,需要创建一个getter函数。 - Brighter side
1
通过使用 friend 关键字? - songyuanyao
2
@PeteBecker 这不是重复的问题。这个问题问“如何”,而那个问题问“为什么”。 - xskxzr
1
事实上,我提出这个问题的目的是想从面向对象的角度得到答案。我所知道的解决方案(强制类型转换、友元声明)只是技术上的把戏。如果仅凭借这些把戏就能让我做到某些事情,那么我可能走错了路。因此,实际的问题是,我做错了什么?为什么我需要牺牲作用域保护或使用技术把戏? - Spiros
1
@GrahamS,保护成员的整个目的是允许派生类使用这些成员,这正是我需要的。基类甚至无需知道存在哪些派生类。使用friend,每当一个新的派生类需要访问受保护的成员时,我需要编辑基类,这违反了开放封闭原则。这不是我正在寻找的干净解决方案。(顺便说一句,我可能希望在基类中隐藏某些数据,通过将其设为私有) - Spiros
显示剩余4条评论
2个回答

6

实际上,使用成员指针存在一个漏洞(无需转换、无需复制):

void B::compare_and_print(const A& other) const
{
  auto max_value = std::max(value, other.*(&B::value));
  std::cout << "Max value: " << max_value << "\n";
}

@YSC 我想我找到了一个安全的选项。 - chtz
好的,这是一个赞,我很惊讶!这让我感到很奇怪,所以我在SO上询问了一个具体的问题,想知道为什么可以这样。看看吧 ;) - YSC

0

您可以通过使用辅助结构来绕过保护:

struct A_Helper : public A
{
    static int GetProtectedValue(const A & a)
    {
        return static_cast<const A_Helper&>(a).Value;
    }
};

您可以在任何地方使用以下代码获取它:A_Helper::GetProtectedValue(a)

在您的情况下,您可以通过static_castreinterpret_castother转换为const B&,但您不知道other实例是否为B类型。使用此转换后的值,读取代码的人会认为otherB类型,并且可能插入导致读取/写入“随机”内存的代码。

请考虑类B具有另一个成员value_B,而otherC类型。使用static_cast<const B&>(other).value_B是未定义行为。


1
这仍然允许任意代码访问受保护的成员,只是让它不那么容易被发现,对吧?通过混淆来实现保护? :-) - Spiros
为什么这样可以,但是直接从B访问就不行呢? - chtz
1
@chtz 忘记了进行静态转换。 - Raxvan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接