【问题标题】:When protected protects too much受保护时保护太多
【发布时间】:2014-03-05 20:03:17
【问题描述】:

这是关于“保护”的,解释为:“当一个类继承另一个类时,派生类的成员可以访问从基类继承的受保护成员。”但是,看看这段代码:

class Base {
public:
  Base( int m ) : member(m){}
protected:
  int member;
};

class Derived : public Base { // one of several subclasses
public:
  Derived( int m ) : Base(m), value(10) {}
  int diff( Base x ){
    return value - x.member;
  }
private:
  int value;
};

派生访问“x.member”,它在其基类中受到保护,对吗?但是编译器会标记一个错误,“Base::member is protected”。而且,在考虑了一分钟之后,我不得不同意编译器。

问题来了:我怎样才能做到这一点,同时将信息隐藏的损失降到最低?

  1. 显然,将“成员”公开编译,但这违背了初衷。
  2. 使用 Base 中的“朋友”机制让子类访问“成员”(以及所有其他私有和受保护的内容)更糟糕(除了超类与其自己的子类的愚蠢绑定 - 维护噩梦)。
  3. 在简单示例中,public int getMember(){ return member; } 是可以接受的。但如果成员的类型是 X*,那么您能做的最好的事情就是 public const X* getMember(){...}。

我错过了什么吗?

【问题讨论】:

  • 只定义int diff( Derived x ),还是不定义?
  • 同样,在阅读您的第 1 点和第 3 点时,可以得出结论,成员保护的意义在于它可以被读取但不能被更改。所以 get 方法会做得很好。在 Base 的构造函数列表中初始化的成员的公共 const-reference 或 const-pointer-to-const 也是可能的。你的第 3 点和“你能做的最好的...... const X*”取决于你想要什么:get 方法无论如何都会返回值。所以你不能用X* get() 改变X* 类型成员的值。只有它指向的东西。
  • 如何创建一个函数int diff(Derived d, Base b) 并让这个函数成为两个类的friend
  • @mb84 diff 使用类型 Base 以使其对所有可能的子类型有用。 (也许 Base* 会使意图更清晰,但它不是我所追求的多态性。) - 我原来的情况是成员的类型为 char*,我不喜欢返回这个指针,即使是 const char*。
  • @rozina 好的,如果没有太多地方需要这种访问权限的话。

标签: c++ inheritance protected


【解决方案1】:

您可以使用静态保护访问器:

class Base {
public:
  Base( int m ) : member(m){}
private:
  int member;
protected:
  static int GetMember(const Base &b)
  { return b.member; }
};

class Derived : public Base { // one of several subclasses
public:
  Derived( int m ) : Base(m), value(10) {}
  int diff( Base &x ){ //beware of your slicing!
    return value - GetMember(x);
  }
private:
  int value;
};

现在让我补充一下我对 C++ 访问控制为什么会这样工作的想法......

C++ 中的访问控制不是关于信息隐藏。这是关于封装。也就是说,简单地说,您过滤掉了对任何使用不当会破坏类的成员的访问。

在理想的班级

  • 公共成员不能用于破坏对象。
  • 私人成员知道他们在做什么。

如您所见,在我的计划中,受保护成员几乎没有位置:

  • 受保护的成员用于实现继承接口(如果有)。

受保护的成员变量的位置更少。

因此,将您的变量设为私有,并编写一个受保护的访问器。访问器必须是静态的才能从派生对象中使用。

【讨论】:

  • “变量没有定义正确的接口”:std::pair 的坏消息 ;-) 当然,它不是作为基类的,所以我真正在争论的是你是否应该说过“变量没有定义适当的继承接口”或类似的东西。
  • @SteveJessop:嗯,爸爸的选择。当然,它们可以是接口的一部分。我的意思是由变量组成的接口是无限的,因此您不能对它们强制使用不变量。如果那是您想要公开的内容;如果没有,请私有化;但是受保护的变量没有什么意义。当然,在某些情况下它们可能有用(我用过),但并不多。
  • 同意,pair 很特别,因为根据定义,它的两个成员是独立的。没有类不变量与它们相关。
  • @rodrigo “人们可能认为信息隐藏是原则,封装是技术”,正如维基百科所说,我倾向于同意作者的观点。最终的意思是一样的。 - 我也不喜欢受保护并尽可能少地使用它。 - 我喜欢静态吸气剂的想法,不过,嗯,...
【解决方案2】:

您可以保留受保护的属性,添加您提到的 getter 函数。至于受保护的指针属性,getter 将确保返回 int(或大对象的 const ref),然后您可以在接受 Derived 和 Base 参数的函数模板中进行区别(getter 为您提供计算值)。

保护数据属性允许直接访问派生类中的受保护属性。您尝试的是访问另一个对象的私有属性。这部分代码:

int diff( Base x ){
    return value - x.member;
  }

相当于写在main:

Base x; 
cout << x.member << endl;

这是一个如何使用您自己建议的选项 3 解决问题的示例,其派生类 PointerDerived 使用指针进行存储:

#include <iostream>

class Base {
public:
  Base( int m ) : member(m){}

  int getMember() const
  {
      return member; 
  }

protected:
  int member;
};

class Derived : public Base { // one of several subclasses
public:
  Derived( int m ) : Base(m), value(10) {}

  int getValue() const
  {
      std::cout << "protected = " << member << std::endl;
      return value; 
  }

private:
  int value;
};

class PointerDerived : public Base { // one of several subclasses
public:

  PointerDerived( int m ) : Base(m), value(new int (10)) {}

  int getValue() const
  {
      std::cout << "protected = " << member << std::endl;
      return *value; 
  }

  ~PointerDerived()
  {
      delete value; 
      value = nullptr;  
  }

private:
  int* value;
};

template<typename Derived, typename Base>
int diff(const Derived& d, const Base& b)
{
    return d.getValue() - b.getMember(); 
}

using namespace std;

int main(int argc, const char *argv[])
{
    PointerDerived p(23); 
    Base q(1);

    cout << diff(p, q) << endl; 

    return 0;
}

由于nullptr,使用-std=c++11编译程序,或者将其更改为NULL

您将diff 设为函数模板,这样您就不必为每个派生类重载它,并让派生类处理存储和访问它,例如PointerDerived .

【讨论】:

  • 其中一些代码回答了一个没有被问到的问题。我不确定这是否会对 OP 有所帮助,更不用说其他任何试图找到原始问题答案的人了。
  • 我刚刚添加了 int* 因为 OP 询问将 X* 作为受保护的数据成员。吸气剂在那里是因为 OP 说它对于一个简单的例子是可以接受的。 diff 是一个函数,因为 getter 确保 PointerDerived 返回一个 int - 对于这个简单的示例。这有意义吗?
猜你喜欢
  • 2011-01-23
  • 2016-02-19
  • 2015-09-05
  • 1970-01-01
  • 1970-01-01
  • 2022-01-12
  • 2020-03-31
  • 2016-08-22
  • 2011-06-06
相关资源
最近更新 更多