指针引用的向上转型

3
我有一个编造的例子(来自真实代码):

我有以下编造的例子(来自真实代码):

template <class T>
class Base {
public:
 Base(int a):x(a) {}
    Base(Base<T> * &other) { }
    virtual ~Base() {}
private:
 int x;
};

template <class T>
class Derived:public Base<T>{
public:
  Derived(int x):Base<T>(x) {}
  Derived(Derived<T>* &other): Base<T>(other) {}

};


int main() {
 Derived<int> *x=new Derived<int>(1);
 Derived<int> y(x);
}

当我尝试编译这段代码时,出现以下错误:

1X.cc: In constructor ‘Derived<T>::Derived(Derived<T>*&) [with T = int]’:
1X.cc:27:   instantiated from here
1X.cc:20: error: invalid conversion from ‘Derived<int>*’ to ‘int’
1X.cc:20: error:   initializing argument 1 of ‘Base<T>::Base(int) [with T = int]

1) 显然gcc被构造函数搞糊涂了。如果我从构造函数中移除引用(reference),那么代码就可以编译通过。因此,我的假设是指向父类的指针引用(up-casting pointer references)出现了问题。有人能告诉我这里到底发生了什么吗?

2) 一个稍微不相关的问题。假如我在构造函数中做了一些可怕的事情,比如"delete other" (请包容我),那么当有人把指向栈上某个东西的指针传给我时会发生什么?

E.g. Derived<int> x(2);
     Derived<int> y(x);

where 

 Derived(Derived<T>*& other) { delete other;}

如何确保指针合法地指向堆上的内容?
5个回答

10

Base<T>Derived<T>的基类型,但Base<T>*不是Derived<T>*的基类型。你可以用一个派生指针代替一个基指针,但你不能用一个派生指针引用来代替一个基指针引用。

原因是,如果你这样做,并且假设Base的构造函数将某个值写入引用中:

Base(Base<T> * &other) {
    Base<T> *thing = new Base<T>(12);
    other = thing;
}
你刚刚将一个指向不是Derived<T>的东西的指针,写入了指向Derived<T>的指针中。编译器无法容忍这种情况发生。

3
  1. 你不能将指向Derived的引用转换为指向Base的引用指针。(模板在此处没有影响,因此在下面的示例中被移除。)
  2. 如果您想要延迟指针的责任,请使用智能指针类型。智能指针类型可以表示原始指针无法表示的“删除责任”。例如,std::auto_ptr和boost::shared_ptr等。

为什么不能进行指针引用上转:

struct Base {};
struct Derived : Base {};
struct Subclass : Base {};

int main() {
  Derived d;
  Derived* p = &d;
  Derived*& d_ptr = p;

  Base*& b_ptr = d_ptr; // this is not allowed, but let's say it is

  Base b;
  b_ptr = &b; // oops! d_ptr no longer points to a Derived!

  Subclass s;
  b_ptr = &s; // oops! d_ptr no longer points to a Derived!
}

当您将“other”参数传递给Base ctor时,您正在尝试执行与上面的b_ptr = d_ptr相同的操作。

2

通过在文档中写明,并依赖调用者遵守,您可以确保指针指向堆上的某个东西。如果调用构造函数的人传递了一个栈指针,那么一切都不确定,这并不是您的错 - 您可以尽早地捕获问题,但不能保证。

这就是标准库的工作方式 - 通常它会捕获明显的错误,但不需要这样做,而且由调用者来确保他们不会做任何愚蠢的事情。


0

不确定为什么你想要指针的引用。为什么不

Base(Base<T> * other) { }

并且

Derived(Derived<T>* other): Base<T>(other) {}

应该可以正常工作。

另外,就像其他回答所说,我不认为你可以合法地知道指针是否指向堆。

编辑:为什么不能做你想要的事情:考虑下面的示例:

Derived1<int> *x = new Derived1<int>
Base<int> **xx =&x;
Derived2<int> y;
*xx = &y;

如果Derived1和Derived2是从Base派生出来的不同类,你认为这是合法的吗?现在x是Derived1*类型,指向Derived2?


你需要引用指针才能改变指针本身。但这不应该是可能的,因为它会允许你将Derived设置为一些Base,而这不是Derived*。 - Michael Krelin - hacker
罗杰,仔细想了想,对我来说这也没有太多意义。 - Michael Krelin - hacker
我表达不够清楚。我想把“other”改为指向其他东西或设置为NULL。如果我没有引用,我将无法这样做,因为我只能使用副本。 - user231536
未知:是的,指针引用是可以的(请参见我的答案),但不能有引用的指针。 - Roger Pate
未知的Google,我刚刚向您解释了为什么它不起作用。将指向Derived的指针转换为指向Base的指针的指针,实际上为您提供了将指向Base的指针强制转换为指向Derived的指针的方法。而要做到这一点,您需要进行粗暴的力量试探。 - Michael Krelin - hacker
显示剩余3条评论

0

你的 x 变量不是指针,如果你想将一个 new Derived<int> 赋值给它,它应该是指针。

至于在堆栈上删除东西,请不要这样做。无法确定你是否已经传递了指向堆栈或堆上某个对象的地址(事实上,C++标准甚至没有承认堆栈的存在)。这里的教训是,你不应该删除你不拥有的东西,特别是如果你无法确定它们来自哪里。


1
一方面,您更正了您的示例;另一方面,它是正确的,因为您可以使用第二个构造函数从指针初始化Derived<int>(或者至少您打算这样做)。 - Michael Krelin - hacker
一个 Derived<int>* 可以作为构造函数参数。因此,你的第一个短语是不正确的... - xtofl
它可能在法律上是允许的,但几乎肯定不是他想要做的,如果是这样,那将是极其糟糕的做法。我从未声称他所做的是非法的。 - Peter Alexander

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