C++ 模板多态

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2203388/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 22:30:23  来源:igfitidea点击:

C++ Templates polymorphism

c++templatespolymorphism

提问by Rusty Horse

I have this structure of classes.

我有这种班级结构。

class Interface{
...
}

class Foo : public Interface{
...
}

template <class T>
class Container{
...
}

And I have this constructor of some other class Bar.

我有一些其他类 Bar 的构造函数。

Bar(const Container<Interface> & bar){
...
}

When I call the constructor this way I get "no matching function" error.

当我以这种方式调用构造函数时,我收到“没有匹配的函数”错误。

Container<Foo> container ();

Bar * temp = new Bar(container);

What is wrong? Are not templates polymorphic?

怎么了?模板不是多态的吗?

回答by Luc Touraille

I think the exact terminology for what you need is "template covariance", meaning that if B inherits from A, then somehow T<B>inherits from T<A>. This is not the case in C++, nor it is with Java and C# generics*.

我认为您需要的确切术语是“模板协方差”,这意味着如果 B 继承自 A,那么以某种方式T<B>继承自T<A>. 这不是 C++ 的情况,也不是 Java 和 C# 泛型*。

There is a good reason to avoid template covariance: this will simply remove all type safety in the template class. Let me explain with the following example:

避免模​​板协变有一个很好的理由:这将简单地删除模板类中的所有类型安全。让我用下面的例子来解释:

//Assume the following class hierarchy
class Fruit {...};

class Apple : public Fruit {...};

class Orange : public Fruit {...};

//Now I will use these types to instantiate a class template, namely std::vector
int main()
{
    std::vector<Apple> apple_vec;
    apple_vec.push_back(Apple()); //no problem here

    //If templates were covariant, the following would be legal
    std::vector<Fruit> & fruit_vec = apple_vec;

    //push_back would expect a Fruit, so I could pass it an Orange
    fruit_vec.push_back(Orange()); 

    //Oh no! I just added an orange in my apple basket!
}

Consequently, you should consider T<A>and T<B>as completely unrelated types, regardless of the relation between A and B.

因此,无论 A 和 B 之间的关系如何,您都应该将T<A>T<B>视为完全不相关的类型。

So how could you solve the issue you're facing? In Java and C#, you could use respectively bounded wildcardsand constraints:

那么你如何解决你面临的问题?在 Java 和 C# 中,您可以分别使用有界通配符约束

//Java code
Bar(Container<? extends Interface) {...}

//C# code
Bar<T>(Container<T> container) where T : Interface {...}

The next C++ Standard (known as C++1x (formerly C++0x)) initially contained an even more powerful mechanism named Concepts, that would have let developers enforce syntaxic and/or semantic requirements on template parameters, but was unfortunately postponed to a later date. However, Boost has a Concept Check librarythat may interest you.

下一个 C++ 标准(称为 C++1x(以前称为 C++0x))最初包含一个名为Concepts 的更强大的机制,它可以让开发人员对模板参数强制执行语法和/或语义要求,但不幸的是被推迟到以后的日子。但是,Boost 有一个您可能会感兴趣的概念检查库

Nevertheless, concepts might be a little overkill for the problem you encounter, an using a simple static assert as proposed by @gfis probably the best solution.

然而,对于您遇到的问题,概念可能有点矫枉过正,使用@gf提出的简单静态断言可能是最好的解决方案。

* Update: Since .Net Framework 4, it is possible to mark generic parameters has being covariant or contravariant.

* 更新:从 .Net Framework 4 开始,可以将泛型参数标记为covariant 或 contravariant

回答by Georg Fritzsche

There are two problems here: default constructions have the form MyClass c;; with parentheses it looks like a function declaration to the compiler.

这里有两个问题:默认构造的形式为MyClass c;;带括号,它看起来像是编译器的函数声明。

The other problem is that Container<Interface>is simply a different type then Container<Foo>- you could do the following instead to actually get polymorphism:

另一个问题是这Container<Interface>只是一种不同的类型Container<Foo>- 您可以执行以下操作来实际获得多态性:

Bar::Bar(const Container<Interface*>&) {}

Container<Interface*> container;
container.push_back(new Foo);
Bar* temp = new Bar(container);

Or of course you could make Baror its constructor a template as Kornel has shown.

或者,当然,您可以Bar像 Kornel 所示那样将其或其构造函数制作为模板。

If you actually want some type-safe compile-time polymorphism, you could use Boost.TypeTraitsis_base_ofor some equivalent:

如果你真的想要一些类型安全的编译时多态性,你可以使用Boost.TypeTraits is_base_of或一些等效的:

template<class T>
Bar::Bar(const Container<T>& c) {
    BOOST_STATIC_ASSERT((boost::is_base_of<Interface, T>::value));
    // ... will give a compile time error if T doesn't 
    // inherit from Interface
}

回答by Kornel Kisielewicz

No. Imagine that the container parameter is "hardcoded" into the class it defines (and that is actually how it works). Hence the container type is Container_Foo, that is not compatible with Container_Interface.

不。想象一下,容器参数被“硬编码”到它定义的类中(这实际上是它的工作方式)。因此容器类型是Container_Foo,与 不兼容Container_Interface

What you might try however is this:

但是,您可以尝试的是:

template<class T>
Bar(const Container<T> & bar){
...
}

Yet you loose direct type checking that way.

然而,你会以这种方式失去直接类型检查。

Actually the STL way (probably more effective and generic) would be to do

实际上 STL 方式(可能更有效和更通用)是这样做的

template<class InputIterator>
Bar(InputIterator begin, InputIterator end){
...
}

... but I assume you don't have iterators implemented in the container.

...但我假设您没有在容器中实现迭代器。

回答by Baltasarq

It is possible to create an inheritance tree for containers, reflecting the inheritance tree of the data. If you have the following data:

可以为容器创建一个继承树,反映数据的继承树。如果您有以下数据:

class Interface {
public:
    virtual ~Interface()
        {}
    virtual void print() = 0;
};

class Number : public Interface {
public:
    Number(int value) : x( value )
        {}
    int get() const
        { return x; }
    void print()
        { std::printf( "%d\n", get() ); };
private:
    int x;
};

class String : public Interface {
public:
    String(const std::string & value) : x( value )
        {}
    const std::string &get() const
        { return x; }
    void print()
        { std::printf( "%s\n", get().c_str() ); }
private:
    std::string x;
};

You could also have the following containers:

您还可以拥有以下容器:

class GenericContainer {
public:
    GenericContainer()
        {}
    ~GenericContainer()
        { v.clear(); }

    virtual void add(Interface &obj)
        { v.push_back( &obj ); }
    Interface &get(unsigned int i)
        { return *v[ i ]; }
    unsigned int size() const
        { return v.size(); }
private:
    std::vector<Interface *> v;
};

class NumericContainer : public GenericContainer {
public:
    virtual void add(Number &obj)
        { GenericContainer::add( obj ); }
    Number &get(unsigned int i)
        { return (Number &) GenericContainer::get( i ); }
};

class TextContainer : public GenericContainer {
public:
    virtual void add(String &obj)
        { GenericContainer::add( obj ); }
    String &get(unsigned int i)
        { return (String &) GenericContainer::get( i ); }
};

This is not the best performing code; it is just to give an idea. The only problem with this approach is that every time you add a new Data class, you have to also create a new Container. Apart from that, you have polymorphism "working again". You can be specific or general:

这不是性能最好的代码;这只是提供一个想法。这种方法的唯一问题是每次添加一个新的 Data 类时,还必须创建一个新的 Container。除此之外,您还有多态性“再次工作”。您可以是具体的或一般的:

void print(GenericContainer & x)
{
    for(unsigned int i = 0; i < x.size(); ++i) {
        x.get( i ).print();
    }
}

void printNumbers(NumericContainer & x)
{
    for(unsigned int i = 0; i < x.size(); ++i) {
        printf( "Number: " );
        x.get( i ).print();
    }
}

int main()
{
    TextContainer strContainer;
    NumericContainer numContainer;
    Number n( 345 );
    String s( "Hello" );

    numContainer.add( n );
    strContainer.add( s );

    print( strContainer );
    print( numContainer );
    printNumbers( numContainer );
}

回答by Adriano Mitre

I propose the following workaround, which employs a template function. Although the example use Qt's QList, nothing prevents the solution from being straightforwardly transposed to any other container.

我提出以下解决方法,它使用模板函数。尽管该示例使用 Qt 的 QList,但没有什么可以阻止该解决方案直接转换到任何其他容器。

template <class D, class B> // D (Derived) inherits from B (Base)
QList<B> toBaseList(QList<D> derivedList)
{
    QList<B> baseList;
    for (int i = 0; i < derivedList.size(); ++i) {
        baseList.append(derivedList[i]);
    }
    return baseList;
}

Pros:

优点:

  • general
  • type-safe
  • fairly efficient if the items are pointers or some other cheaply copy-constructible elements (such as implicitly shared Qt classes)
  • 一般的
  • 类型安全
  • 如果项目是指针或其他一些廉价的可复制构造的元素(例如隐式共享的 Qt 类),则相当有效

Cons:

缺点:

  • requires the creation of a new container, as opposed to enabling the reuse of the original one
  • implies some memory and processor overhead both to create and to populate the new container, which depend heavily on the cost of the copy-constructor
  • 需要创建一个新容器,而不是重用原始容器
  • 意味着一些内存和处理器开销来创建和填充新容器,这在很大程度上取决于复制构造函数的成本

回答by user3015856

#include <iostream>
#include <sstream>
#include <map>
#include <vector>

struct Base { int b = 111; };
struct Derived: public Base { };

struct ObjectStringizer {
    template <typename T>
    static std::string to_string(const T& t) {
        return helper<T>()(t);
    }

    template <typename T, typename = void>
    struct helper {
        std::string operator()(const T& t) {
            std::ostringstream oss;
            oss << t;
            return oss.str();
        }
    };

    template <typename T>
    struct helper<T, typename std::enable_if<std::is_base_of<Base, T>::value>::type> {
        std::string operator()(const T& base) {
            return to_string(base.b);
        }
    };

    template <typename T>
    struct helper<std::vector<T>> {
        std::string operator()(const std::vector<T>& v) {
            std::ostringstream oss;
            for (size_t i = 0, sz = v.size(); i < sz; ++i) {
                oss << (i ? "," : "") << to_string(v[i]);
            }
            return "[" + oss.str() + "]";
        }
    };

    template <typename Key, typename Value>
    struct helper<std::map<Key, Value>> {
        std::string operator()(const std::map<Key, Value>& m) {
            std::ostringstream oss;
            for (auto iter = m.begin(), iter_end = m.end(); iter_end != iter; ++iter) {
                oss << (m.begin() != iter ? "," : "") << to_string(iter->first) << ":" << to_string(iter->second);
            }
            return "{" + oss.str() + "}";
        }
    };
};

int main(int argc, char* argv[]) {
    std::cout << ObjectStringizer::to_string("hello ") << ObjectStringizer::to_string(std::string("world")) << std::endl;
    std::cout << ObjectStringizer::to_string(Derived()) << std::endl;
    std::cout << ObjectStringizer::to_string(std::vector<int>{3, 5, 7, 9}) << std::endl;
    std::cout << ObjectStringizer::to_string(std::map<int, std::string>{{1, "one"}, {2, "two"}}) << std::endl;
    return 0;
}

回答by pm100

container is a container of Foo objects not a container of Interface objects

容器是 Foo 对象的容器,而不是 Interface 对象的容器

And it cannot be polymorphic either, pointers to things can be ,but not the objects themselvs. How big would the slots in the container have to be for container if you could put anything derived from interface in it

它也不能是多态的,指向事物的指针可以是多态的,但不能是对象本身。如果您可以将任何从接口派生的内容放入容器中,那么容器中的插槽必须有多大

you need

你需要

 container<Interface*>

or better

或更好

 container<shared_ptr<Interface> >