问题
今天在做项目时碰到了一个问题。简化后的代码如下:
class Expression {
public:
virtual ~Expression() {}
};
class BinaryExpression : public Expression {
public:
OpCode op_;
std::shared_ptr<Expression> left_;
std::shared_ptr<Expression> right_;
};
现在我尝试将一个BinaryExpression指针赋值给Expression指针,简化后的代码如下:
auto binary_expr = std::make_shared<BinaryExpression>();
// do something...
std::shared_ptr<Expression> term = binary_expr;
经测试,代码可以正常通过编译。
可当我将BinaryExpression类的继承代码修改为class BinaryExpression : private Expression后,编译器却抛出了一长串错误:
no operator "=" matches these operandsC/C++(349)
parser.cc(50, 19): operand types are: std::shared_ptr<Expression> = std::shared_ptr<BinaryExpression>
parser.cc(50, 19): candidate function template "std::shared_ptr<_Tp>::operator=(std::unique_ptr<_Yp, _Del> &&__r) [with _Tp=Expression]" failed deduction
parser.cc(50, 19): candidate function template "std::shared_ptr<_Tp>::operator=(std::shared_ptr<_Yp> &&__r) noexcept [with _Tp=Expression]" failed deduction
parser.cc(50, 19): function "std::shared_ptr<_Tp>::operator=(std::shared_ptr<_Tp> &&__r) noexcept [with _Tp=Expression]" does not match because argument #1 does not match parameter
parser.cc(50, 19): candidate function template "std::shared_ptr<_Tp>::operator=(std::auto_ptr<_Yp> &&__r) [with _Tp=Expression]" failed deduction
parser.cc(50, 19): candidate function template "std::shared_ptr<_Tp>::operator=(const std::shared_ptr<_Yp> &__r) noexcept [with _Tp=Expression]" failed deduction
parser.cc(50, 19): function "std::shared_ptr<_Tp>::operator=(const std::shared_ptr<_Tp> &) noexcept [with _Tp=Expression]" does not match because argument #1 does not match parameter
我刚开始以为是智能指针的问题,可是我把智能指针去掉换成裸指针后,尝试编译仍然会抛出错误。
原因
经查阅资料,这个问题的根源在于,C++中公有继承(public inheritance) 和 私有继承(private inheritance) 在语言设计之初就被赋予了不同的功能和涵义,或者说语义:
- 公有继承(public inheritance) :
- 公有继承表示is-a关系。
- 具体来说,派生类是基类的子类型(subtype),派生类支持基类的所有接口,隐式转换是合理的。
- 例如:
Dog公有继承Animal,则Dog*可隐式转为Animal*,因为狗“是”动物。
- 私有继承(private inheritance) :
- 私有继承表示implemented-in-terms-of关系。
- 具体来说,仅表示派生类复用基类的实现细节,不对外暴露基类的接口。
- 例如:
Stack私有继承std::vector,表示栈用向量实现,但不允许外部通过vector*直接操作栈的内部逻辑。
从另一个角度来说,私有继承的语义更接近组合(“有一个”关系),而非公有继承(“是一个”关系)。显而易见,根据我们的开发经验,当你在使用组合关系来实现某个类中的某些功能时,你大多数时候应该是不会将被组合进类中的那个对象及其方法给暴露出来的——这与私有继承的行为类似!
因此,若允许将私有继承自基类的派生类的对象指针,隐式转换为积累的对象指针,则外部代码可通过基类指针绕过派生类的封装,直接操作基类接口,违背私有继承的设计初衷。