是否有可能指向不同的模板类型可转换?

时间:2009-06-17 14:29:01

标签: c++

我有一个模板类,它会将某些信息与类型捆绑在一起:

template <typename T>
class X
{
  int data[10]; // doesn't matter what's here really
  T t;
public:
  // also not terribly relevant
};

然后我们假设我们有一个Base类和Derived类:

class Base {};

class Derived : public Base {};

我希望能够做到这样的事情(但我认为不行):

void f(shared_ptr<X<Base> > b);

shared_ptr<X<Derived> > d(new X<Derived>);

f(d);

如果T *可以转换为Y *,有没有办法可以指向X<T>指向X<Y>的指针?

5 个答案:

答案 0 :(得分:2)

将问题改为更一般:如果不相关的类型本身可以转换,是否可以转换不相关类型的指针?,答案不是。不同的模板实例定义了不同的不相关类型。

您可以隐式转换(请参阅下面的注释)指向派生对象的指针到基础对象的指针(由于访问或歧义而适用某些限制),或者您可以从基指针到派生指针进行显式转换(再次有一些限制)。您也可以来回转换为void *,但转换为不相关的类型最终会导致未定义的行为。

有明显的理由指出不能从不相关的类型转换。第一个是将一个指针类型转换为另一个指针类型将不会转换指向的内存。也就是说,您可以将int转换为double,但将指向int的指针转换为指向double的指针会使编译器认为指向的地址有8个字节的内存,而实际上只有4个字节存在(假设32位或64位架构,32位整数和64位双精度)。取消引用指针肯定会结束。

标准中的注释:

标准的4.10节处理指针转换,第一段处理空指针转换,第二段处理 void 指针转换。第三段声明只要B是D的基类,就可以将指向类型D的指针转换为类型B的指针。在那里没有定义其他指针转换。在第5.4节中指定了显式转换,第7段处理指针转换,只在B是B的基数时,只在几种情况下添加从B *到D *的可能显式转换。

答案 1 :(得分:0)

修改: 你想做的是可能的;您可以使用可转换性测试和boost库提供的静态断言,以便在类型不兼容时生成编译时错误。
这是一个例子:

#include <string>   
#include <iostream> 
#include <boost/type_traits/is_convertible.hpp>
#include <boost/static_assert.hpp>

template<typename MyType>
class MyTypeInfo{
public:
    MyType* myType;

    MyTypeInfo(MyType* _myType){
        myType = _myType;
    }

    template <typename DestinationType>
    operator MyTypeInfo<DestinationType>() {
        BOOST_STATIC_ASSERT((boost::is_convertible<MyType,DestinationType>::value));
        MyTypeInfo<DestinationType> ret((DestinationType*)myType);
        return ret;
    }

};

class Base{
public:
    virtual void do_something(){
        std::cout << "Base" << std::endl;
    }
};
class Derived : public Base{
public:
    virtual void do_something(){
        std::cout << "Derived" << std::endl;
    }
};

class NonDerived{
};

int _tmain(int argc, _TCHAR* argv[])
{
    // this one is ok
    MyTypeInfo<Derived> d(new Derived());
    MyTypeInfo<Base> b = (MyTypeInfo<Base>)d;
    b.myType->do_something();

    // this one fails to compile
    //MyTypeInfo<NonDerived> nd(new NonDerived());
    //MyTypeInfo<Base> b2 = (MyTypeInfo<Base>)nd;
    //b2.myType->do_something();

    return 0;
}

如果您不想或不能使用boost库,可以参考Andrei Alexandrescu's wonderful book "Modern C++ Design"获取可转换性测试和静态断言宏的可能实现。

答案 2 :(得分:0)

天儿真好,

你有没有想过为什么要这样做?

回到基础知识,继承是针对不同类型的派生对象的特殊行为,例如:在名为Shape的基类中声明的draw()函数将为派生类Square生成与派生类Circle相比的不同输出。

如果您想要相同类型的行为,无论类型如何,都应使用模板,例如:整数堆栈中的pop函数的行为方式与浮点堆栈中的pop函数的行为方式与Foo类型的对象堆栈中的pop函数的行为方式相同。

HTH

欢呼声,

答案 3 :(得分:0)

在重新思考这个问题时,我才意识到你遇到了另一个问题。多态行为只能通过指针和/或引用来实现。尝试使用值生成所谓的 slicing ,其中原始派生对象被转换(复制)到基础对象中,从而丢失所有额外信息。

切片的常见例子是:

class Base 
{
public:
   virtual void polymorphic() { 
      std::cout << "Base" << std::endl;
   }
};
class Derived : public Base
{
public:
   virtual void polymorphic() {
      std::cout << "Derived" << std::endl;
   }
};
void f( Base b ) {
   b.polymorphic();
}
int main() {
   Derived d;
   f( d ); // prints Base
}

现在,原始问题涉及指针类型的转换,我回答before。我相信您的代码会提供切片问题的变体。我们可以使用未定义的行为来强制从不相关的类型转换指针:

// same Base and Derived definitions as above
template <typename T>
struct Holder
{
   T data;
};
int main() {
   Holder<Derived> derived;
   Holder<Derived>* derived_ptr = &derived;
   Holder<Base>* base_ptr = reinterpret_cast<Holder<Base>* >( derived_ptr ); // undefined!!!
   std::cout << "derived_ptr=" << derived_ptr << ", base_ptr=" << base_ptr << std::endl;
   base_ptr->data.polymorphic(); // prints Base
}

首先,根据标准,这是未定义的行为......但请耐心等待。我们可以尝试使用通用编译器可以实现的功能。我们有一个 Holder 类型的对象,以及一个指向它的指针( derived_ptr ),现在我们将指针重新解释为指向 Holder 的指针。此时 base_ptr derived_ptr 具有相同的值,它们指向相同的地址(检查输出)。

在最后一步中,我们访问&amp; derived 中存储的 Holder 的数据字段,并在其上调用虚函数* polymorphic()*。 。但是在 Holder 中, data 字段不是引用或指针,它是 Base 类型的元素,因此编译器可以决定它将调用它的 Base 版本,在g ++ 4.0中也是如此。

这是所有未定义的行为,因此请使用您选择的编译器进行尝试,最终可能会产生相同或不同的结果。

如果更改模板定义以存储指针,则会获得期望的多态行为。也就是说,在这种情况下,预期知道手头对象的内存占用量。并非您可以期待来自意外的未定义行为的任何内容。

答案 4 :(得分:0)

我不想指出显而易见但是有什么理由你不能把这个功能变成模板吗?即。

template <typename T>
void f(shared_ptr<T> b);

shared_ptr<X<Derived> > d(new X<Derived>);

f(d);