在C ++中实现“有界通用性”

时间:2011-07-23 20:24:27

标签: c++ templates generics

我正在将项目从Java转移到C ++,而我在Java中遇到一些相对简单的问题。

我有一个类X,用于处理Y类型的对象和从Y继承的对象。 X经常需要调用来自Y的方法,比如kewl_method(),并且此方法在从Y继承的每个类中都有所不同。在Java中,我可以这样做:

public class X<y extends Y>

我会在kewl_method()中致电X而没有任何头痛,它会做我想要的。如果我理解正确(我是C ++的新手),C ++中没有有限的通用性,所以如果我使用X的模板,就可以用绝对的东西来填充它,我就赢了能够调用kewl_method()

的变体

在C ++中执行此操作的最佳方法是什么?使用演员?

限制:我不能使用boost或TR1。

6 个答案:

答案 0 :(得分:13)

TravisG(是:heishe)already answered,就我而言。

但我想对你的问题发表评论:

  

所以,如果我使用带X的模板,就可以用绝对的任何东西填充它

不,因为如果没有可访问的kewl_method,它就无法编译。

你必须记住,在Java中,有限的通用性不是像你想象的那样限制你的泛型类接受的类型,而是更多关于为泛型类提供关于它的泛型类型T的更完整的信息,以便能够在编译时验证对其方法的调用。

在C ++中,此功能按原样提供并由编译器使用:在类似于duck typing的方式中,但在编译时解析,编译器将仅在泛型类型时接受方法的编译class可以访问kewl_method

有关4个班级的示例:

class X
{
    public : virtual void kewl_method() { /* etc. */ }
} ;

class Y : public X
{
    public : virtual void kewl_method() { /* etc. */ }
} ;

class Z
{
    public : virtual void kewl_method() { /* etc. */ }
} ;

class K
{
    public : virtual void wazaa() { /* etc. */ }
} ;

普通C ++解决方案

使用C ++模板,您可以提供模板化的类A:

template<typename T>
class A
{
    public :
        void foo()
        {
            T t ;
            t.kewl_method() ;
        }
} ;

...使用X,Y和Z类,但不是K,因为:

  • X:它实现了kewl_method()
  • Y:它公开来自X,它实现了kewl_method()
  • Z:它实现了kewl_method()
  • K:它没有实现kewl_method()

...它比Java(或C#)的泛型更强大。用户代码为:

int main()
{
    // A's constraint is : implements kewl_method
    A<X> x ; x.foo() ; // OK: x implements kewl_method
    A<Y> y ; y.foo() ; // OK: y derives from X
    A<Z> z ; z.foo() ; // OK: z implements kewl_method
    A<K> k ; k.foo() ; // NOT OK : K won't compile: /main.cpp error:
                       //   ‘class K’ has no member named ‘kewl_method’
    return 0;
}

您需要调用foo()方法来阻止编译。

如果确实需要约束怎么办?

如果你想明确地将它限制为继承自X的类,你必须自己使用代码(直到C++ concepts标准化......)它们错过了C ++ 0x截止日期,所以我想我们将不得不等待下一个标准......

如果确实想要放置约束,有多种方法。 虽然我知道它,但我不熟悉SFINAE概念给你解决方案,但是我仍然可以看到两种方法来应用约束对于你的情况(当他们测试g ++ 4.4.5时,有人可以更聪明地验证我的代码吗?):

添加未使用的演员?

B类类似于A类,另外还有一行代码:

template<typename T> // We want T to derive from X
class B
{
    public :
        void foo()
        {
            // creates an unused variable, initializing it with a
            // cast into the base class X. If T doesn't derive from
            // X, the cast will fail at compile time.
            // In release mode, it will probably be optimized away
            const X * x = static_cast<const T *>(NULL) ;

            T t ;
            t.kewl_method() ;
        }
} ;

当调用B :: foo()时,只有在T*可以强制转换为X*时才会编译(只有通过公共继承才能进行编译)。

结果将是:

int main()
{
    // B's constraint is : implements kewl_method, and derives from X
    B<X> x ; x.foo() ; // OK : x is of type X
    B<Y> y ; y.foo() ; // OK : y derives from X
    B<Z> z ; z.foo() ; // NOT OK : z won't compile: main.cpp| error:
                       //      cannot convert ‘const Z*’ to ‘const X*’
                       //      in initialization
    B<K> k ; k.foo() ; // NOT OK : K won't compile: /main.cpp error:
                       //      cannot convert ‘const K*’ to ‘const X*’
                       //      in initialization
    return 0 ;
}

但是,作为A示例,您需要调用foo()方法来阻止编译。

使用类添加本地“约束”?

让我们创建一个表达其构造函数约束的类:

template<typename T, typename T_Base>
class inheritance_constraint
{
    public:
        inheritance_constraint()
        {
            const T_Base * t = static_cast<const T *>(NULL) ;
        }
} ;

你会注意到这个类是空的,它的构造函数什么都不做,所以很有可能它会被优化掉。

您可以使用它,如下例所示:

template<typename T>
class C : inheritance_constraint<T, X> // we want T to derive from X
{
    public :
        void foo()
        {
            T t ;
            t.kewl_method() ;
        }
} ;

私有继承意味着你的“inheritance_constraint”不会搞砸你的代码,但是,它仍然会在编译时表达一个约束,该约束将停止不从X派生的类T的编译:

结果将是:

int main()
{
    // C's constraint is : implements kewl_method, and derives from X
    C<X> x ; // OK : x is of type X
    C<Y> y ; // OK : y derives from X
    C<Z> z ; // NOT OK : z won't compile: main.cpp error:
             //      cannot convert ‘const Z*’ to ‘const X*’
             //      in initialization
    C<K> k ; // NOT OK : K won't compile: /main.cpp error:
             //      cannot convert ‘const K*’ to ‘const X*’
             //      in initialization
    return 0 ;
}

问题在于它依赖于继承和构造函数调用才有效。

使用函数添加本地“约束”?

此约束更像是静态断言,在调用方法时进行测试。首先,约束函数:

template<typename T, typename T_Base>
void apply_inheritance_constraint()
{
    // This code does nothing, and has no side effects. It will probably
    // be optimized away at compile time.
    const T_Base * t = static_cast<const T *>(NULL) ;
} ;

然后使用它的课程:

template<typename T>
class D
{
    public :
        void foo()
        {
            // Here, we'll verify if T  inherits from X
            apply_inheritance_constraint<T, X>() ;

            T t ;
            t.kewl_method() ;
        }
} ;

int main()
{
    // D's constraint is : implements kewl_method, and derives from X
    D<X> x ; // OK : x is of type X
    D<Y> y ; // OK : y derives from X
    D<Z> z ; // NOT OK : z won't compile: main.cpp error:
             //      cannot convert ‘const Z*’ to ‘const X*’
             //      in initialization
    D<K> k ; // NOT OK : K won't compile: /main.cpp 2 errors:
             //      ‘class K’ has no member named ‘kewl_method’
             //      cannot convert ‘const K*’ to ‘const X*’
             //      in initialization
    return 0 ;
}

但是,作为A和B示例,您需要调用foo()方法来阻止编译。

结论

根据您的具体需要,您必须在上述方法之一中进行选择。

但通常情况下,就我而言,我发现所有这些都太过分了,我会使用上面第一个更简单的解决方案。

编辑2011-07-24

添加了另一个带有代码的部分,通过简单的函数调用来表达约束。

在“添加未使用的演员?”中部分,我用指针转换替换了引用转换X & x = t ;(如在其他部分中),我认为这更好。

为了给凯撒带来应有的价值,指针演员最初的灵感来自now deleted answer Jonathan Grynspan中的一行代码。

答案 1 :(得分:4)

只需输入:

template<class T>
void method()
{
   T t;
   t.kewl_method();
}

如果你以错误的方式使用它,你的编译器会给你一个错误,说kewl_method()不是[type of T]的成员。

答案 2 :(得分:2)

如果我理解你正确地使Y::kewl_method虚拟是一个很好的Java翻译。你要在从Y派生的类中重载它。

答案 3 :(得分:0)

您在Java中引用的构造是泛型。在C ++中找到的最接近的对应物是模板。 因此,您在C ++中提供的最接近的翻译将是

template<class T>
class X
{
    // Here you use T as Y or type derived from it
    // for example
    void method(T& y)
    {
        y.kewl_method();
    }
};

用法如下:

X<Y> a;

或者,如果您来自Y类型,请说Z

class Z : public Y {
  // ...
};

您也可以使用

X<Z> b;

这是可以接受的。模板是用C ++编写的,即你可以调用T类型的任何方法,只要实际指定的类型具有这种方法,它就是正确的。

答案 4 :(得分:0)

据我了解,您希望强制传递给您的类模板的模板参数继承自某个class Y。我应该注意到,你不能使用助推器是非常不幸的。我将提供解决方案 提升。

template <class T, bool OK> class XImpl; //no definition, just declaration
template <class T> class XImpl<T, true> //partial specialization
{
    //here goes your real class definition
};
template <class T> class X : public XImpl<T, boost::is_base_and_derived<Y, T>::value >
{}; //if T isn't derived from Y, compilation will fail because you are trying to 
    //inherit from an incomplete (undefined) type 

我们使用的唯一boost部分是is_based_and_derived元函数。您可以查看boost源,了解它是如何实现的,并自行实现。 HTH

答案 5 :(得分:0)

使用模板,但对其使用的类型加以限制:

template<typename T>
void
X::some_member(T& t)
{
    // Triggers a compile-time error if/when T is not derived from Y
    STATIC_ASSERT(( is_base_of<Y, T> ));
    t.kewl_method();
}

由于您未使用Boost,因此您必须实施STATIC_ASSERTis_base_of。第一个有点容易,但第二个不是这样(尽管你可以使用convertible<T, Y>特征作为近似值。)