能否防止在编译时通过父类调用继承的私有成员?
Can you prevent inherited private members being called through the parent at compile time?
如果你有一个功能丰富的类,可能是一个你不拥有/控制的类,通常情况下,你想添加一些功能,所以派生是有意义的。
有时候你也想做减法,那就是禁止基本接口的某些部分。我所见过的常见习惯用法是派生并使某些成员函数为私有,然后不实现它们。如下:
class Base
{
public:
virtual void foo() {}
void goo() { this->foo(); }
};
class Derived : public Base
{
private:
void foo();
};
别的地方:
Base * b= new Derived;
和另一个位置:
b->foo(); // Any way to prevent this at compile time?
b->goo(); // or this?
似乎如果编译不知道它是派生的,你能做的最好的事情就是不实现,让它在运行时失败。
问题出现在当你有一个库,你不能改变,它接受一个指向基的指针,你可以实现一些方法,但不是全部。因此,部分库是有用的,但如果在编译时不知道哪些函数将调用哪些函数,则可能会冒核心转储的风险。
使之更加困难的是,其他人可能继承了你的类并想要使用这个库,他们可能会添加一些你没有添加的函数。
还有别的办法吗?在c++中11吗?在c++中14 ?
让我们分析一下,主要集中在两个方面:
class Base
{
public:
virtual void foo() {} // This 1)
// ...
class Derived : public Base // and this 2)
在1)中,您告诉世界Base
的每个对象都公开提供foo()
方法。这意味着当我有Base*b
时,我可以调用b->foo()
-和b->goo()
。
在2)中,你告诉世界你的类Derived
公开表现得像Base
。因此,以下是可能的:
void call(Base *b) { b->foo(); }
int main() {
Derived *b = new Derived();
call(b);
delete b;
}
希望你看到call(Base*)
无法知道b
是否是派生的,因此它不可能在编译时决定调用foo
是否合法。
有两种处理方法:
- 你可以改变
foo()
的可见性。这可能不是您想要的,因为其他类可以从Base
派生,而有人终究想调用foo
。请记住,虚拟方法可以是私有的,因此您可能应该将Base
声明为
class Base
{
virtual void foo() {}
public:
void goo() { this->foo(); }
};
- 您可以更改
Derived
,使其从Base
继承protected
或private
。这意味着没有人/只有继承类才能"看到"Derived
是Base
,并且不允许调用foo()
/goo()
:
class Derived : private Base
{
private:
void foo() override;
// Friends of this class can see the Base aspect
// .... OR
// public: // this way
// void foo(); // would allow access to foo()
};
// Derived d; d.goo() // <-- illegal
// d.foo() // <-- illegal because `private Base` is invisible
您通常应该选择后者,因为它不涉及更改Base
类的接口-"真正的"实用程序。
TL;DR:派生类是提供至少该接口的契约。不可能。
这似乎是你想要做的:
struct Library {
int balance();
virtual int giveth(); // overrideable
int taketh(); // part of the library
};
/* compiled into the library's object code: */
int Library::balance() { return giveth() - taketh(); }
/* Back in header files */
// PSEUDO CODE
struct IHaveABadFeelingAboutThis : public Library {
int giveth() override; // my implementation of this
int taketh() = delete; // NO TAKE!
};
所以你不能在IHaveABadFeelingAboutThis
上调用taketh()
,即使它被强制转换为基类。
int main() {
IHaveABadFeelingAboutThis x;
Library* lib = &x;
lib->taketh(); // Compile error: NO TAKE CANDLE!
// but how should this be handled?
lib->balance();
}
如果你想呈现一个与底层库不同的接口,你需要一个facade来呈现你的接口,而不是库的接口。
class Facade {
struct LibraryImpl : public Library {
int giveth() override;
};
LibraryImpl m_impl;
public:
int balance() { return m_impl.balance(); }
virtual int giveth() { return m_impl.giveth(); }
// don't declare taketh
};
int main() {
Facade f;
int g = f.giveth();
int t = f.taketh(); // compile error: undefined
}
虽然我不认为你的整体情况是好的设计,我也在评论中分享了很多观点,但我也能理解你不控制的代码是如何参与的。我不相信有任何编译时解决方案可以很好地定义您的问题,但是比起使方法私有而不实现它们,更可取的是实现整个接口,并简单地使任何无法处理的方法抛出异常。这样至少定义了行为,如果您认为可以从需要您无法提供的接口的库函数中恢复,您甚至可以使用try/catch。
如果您有class A:public B
,那么您应该遵循https://en.wikipedia.org/wiki/Liskov_substitution_principle
Liskov替换原则是指向a的指针在任何情况下都可以用作指向b的指针。B
有什么要求,A
也应该满足。
这很难实现,这也是为什么许多人认为oo风格的继承远没有看起来那么有用的原因之一。
您的base
暴露了virtual void foo()
。通常的契约意味着这样的foo
可以被调用,如果满足它的前提条件,它将返回。
如果从base
派生,则不能加强前置条件,也不能放松后置条件。
另一方面,如果记录了base::foo()
(并且支持base
的消费者),那么它抛出错误的可能性(例如method_does_not_exist
),那么您可以派生并让您的实现抛出该错误。请注意,即使合约说它可以这样做,在实践中,如果没有测试,消费者可能无法工作。
违反Liskov替代原则是产生大量bug和不可维护代码的好方法。只有当你真的、真的需要的时候才去做
- C++继承 - 为什么调用父方法?
- C++模板类 - 继承调用错误的函数
- 继承:调用基类的成员和方法
- C++意外调用继承类的函数
- 如何在 c++ 多重继承中调用父非虚函数?
- 如何在继承层次结构中调用具有默认参数的构造函数?
- 如何调用继承的重载运算符<<并在派生类的输出中添加更多文本?
- 从基类调用继承类的方法.C++
- 派生类调用使用非继承成员的继承函数
- 在 lambda 函数 g++-4.8 中调用继承的受保护子类型
- 虚拟继承:调用没有匹配函数
- 多重继承:调用所有覆盖的函数
- 使用模板化继承调用祖父构造函数
- C++ 继承:调用带参数的构造函数
- C++继承:调用重载基类函数
- C++继承:调用父类构造函数
- 类继承调用不同的构造函数
- 用多重继承调用c++中被覆盖的函数
- visual C++/MFC多重继承调用基类构造函数
- 如何避免使用虚继承调用过多参数化的构造函数