C++ 中静态多态背后的动机是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19062733/
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
What is the motivation behind static polymorphism in C++?
提问by Martin Drozdik
I understand the mechanics of static polymorphismusing the Curiously Recurring Template Pattern. I just do not understand what is it good for.
我了解使用Curiously Recurring Template Pattern的静态多态机制。我只是不明白它有什么好处。
The declared motivation is:
声明的动机是:
We sacrifice some flexibility of dynamic polymorphism for speed.
我们为了速度牺牲了一些动态多态的灵活性。
But why bother with something so complicatedlike:
但是为什么要费心处理这么复杂的事情,比如:
template <class Derived>
class Base
{
public:
void interface()
{
// ...
static_cast<Derived*>(this)->implementation();
// ...
}
};
class Derived : Base<Derived>
{
private:
void implementation();
};
When you can just do:
当你可以这样做时:
class Base
{
public:
void interface();
}
class Derived : public Base
{
public:
void interface();
}
My best guess is that there is no semantic difference in the code and that it is just a matter of good C++ style.
我最好的猜测是代码中没有语义差异,这只是一个好的 C++ 风格的问题。
Herb Sutter wrote in Exceptional C++ style: Chapter 18
that:
赫伯·萨特写道Exceptional C++ style: Chapter 18
:
Prefer to make virtual functions private.
更喜欢将虚拟函数设为私有。
Accompanied of course with a thorough explanation why this is good style.
当然伴随着一个彻底的解释为什么这是好的风格。
In the context of this guideline the first example is good, because:
在本指南的上下文中,第一个示例很好,因为:
The void implementation()
function in the example can pretend to be virtual, since it is here to perform customization of the class. It therefore should be private.
void implementation()
示例中的函数可以假装是虚拟的,因为它在这里执行类的自定义。因此它应该是私有的。
And the second example is bad, since:
第二个例子不好,因为:
We should not meddle with the public interface to perform customization.
我们不应该干预公共接口来执行定制。
My question is:
我的问题是:
- What am I missing about static polymorphism? Is it all about good C++ style?
- When should it be used? What are some guidelines?
- 我对静态多态性缺少什么?这都是关于良好的 C++ 风格吗?
- 应该什么时候使用?有哪些指导方针?
回答by Billy ONeal
What am I missing about static polymorphism? Is it all about good C++ style?
我对静态多态性缺少什么?这都是关于良好的 C++ 风格吗?
Static polymorphism and runtime polymorphism are different things and accomplish different goals. They are both technically polymorphism, in that they decide which piece of code to execute based on the type of something. Runtime polymorphism defers binding the type of something (and thus the code that runs) until runtime, while static polymorphism is completely resolved at compile time.
静态多态和运行时多态是不同的东西,完成不同的目标。从技术上讲,它们都是多态性的,因为它们根据某事物的类型决定要执行哪一段代码。运行时多态性将某些类型(以及运行的代码)的绑定推迟到运行时,而静态多态性在编译时完全解决。
This results in pros and cons for each. For instance, static polymorphism can check assumptions at compile time, or select among options which would not compile otherwise. It also provides tons of information to the compiler and optimizer, which can inline knowing fully the target of calls and other information. But static polymorphism requires that implementations be available for the compiler to inspect in each translation unit, can result in binary code size bloat (templates are fancy pants copy paste), and don't allow these determinations to occur at runtime.
这导致每个人的利弊。例如,静态多态可以在编译时检查假设,或者在否则不会编译的选项中进行选择。它还为编译器和优化器提供了大量信息,它们可以内联完全了解调用的目标和其他信息。但是静态多态要求编译器可以在每个翻译单元中检查实现,这会导致二进制代码大小膨胀(模板是花哨的复制粘贴),并且不允许在运行时进行这些确定。
For instance, consider something like std::advance
:
例如,考虑以下内容std::advance
:
template<typename Iterator>
void advance(Iterator& it, ptrdiff_t offset)
{
// If it is a random access iterator:
// it += offset;
// If it is a bidirectional iterator:
// for (; offset < 0; ++offset) --it;
// for (; offset > 0; --offset) ++it;
// Otherwise:
// for (; offset > 0; --offset) ++it;
}
There's no way to get this to compile using runtime polymorphism. You have to make the decision at compile time. (Typically you would do this with tag dispatch e.g.)
没有办法使用运行时多态来编译它。您必须在编译时做出决定。(通常你会用标签调度来做到这一点,例如)
template<typename Iterator>
void advance_impl(Iterator& it, ptrdiff_t offset, random_access_iterator_tag)
{
// Won't compile for bidirectional iterators!
it += offset;
}
template<typename Iterator>
void advance_impl(Iterator& it, ptrdiff_t offset, bidirectional_iterator_tag)
{
// Works for random access, but slow
for (; offset < 0; ++offset) --it; // Won't compile for forward iterators
for (; offset > 0; --offset) ++it;
}
template<typename Iterator>
void advance_impl(Iterator& it, ptrdiff_t offset, forward_iterator_tag)
{
// Doesn't allow negative indices! But works for forward iterators...
for (; offset > 0; --offset) ++it;
}
template<typename Iterator>
void advance(Iterator& it, ptrdiff_t offset)
{
// Use overloading to select the right one!
advance_impl(it, offset, typename iterator_traits<Iterator>::iterator_category());
}
Similarly, there are cases where you really don't know the type at compile time. Consider:
同样,有些情况下您在编译时确实不知道类型。考虑:
void DoAndLog(std::ostream& out, int parameter)
{
out << "Logging!";
}
Here, DoAndLog
doesn't know anything about the actual ostream
implementation it gets -- and it may be impossible to statically determine what type will be passed in. Sure, this can be turned into a template:
在这里,对它获得DoAndLog
的实际ostream
实现一无所知——并且可能无法静态确定将传入的类型。当然,这可以变成一个模板:
template<typename StreamT>
void DoAndLog(StreamT& out, int parameter)
{
out << "Logging!";
}
But this forces DoAndLog
to be implemented in a header file, which may be impractical. It also requires that all possible implementations of StreamT
are visible at compile time, which may not be true -- runtime polymorphism can work (although this is not recommended) across DLL or SO boundaries.
但这迫使DoAndLog
在头文件中实现,这可能不切实际。它还要求所有可能的实现StreamT
在编译时都是可见的,这可能不是真的——运行时多态可以跨 DLL 或 SO 边界工作(尽管不推荐这样做)。
When should it be used? What are some guidelines?
应该什么时候使用?有哪些指导方针?
This is like someone coming to you and saying "when I'm writing a sentence, should I use compound sentences or simple sentences"? Or perhaps a painter saying "should I always use red paint or blue paint?" There is no right answer, and there is no set of rules that can be blindly followed here. You have to look at the pros and cons of each approach, and decide which best maps to your particular problem domain.
这就像有人来找你说“当我写一个句子时,我应该使用复合句还是简单句”?或者也许一个画家说“我应该总是使用红色颜料还是蓝色颜料?” 没有正确的答案,也没有一套可以盲目遵循的规则。您必须查看每种方法的优缺点,并决定哪种方法最适合您的特定问题域。
As for the CRTP, most use cases for that are to allow the base class to provide something in terms of the derived class; e.g. Boost's iterator_facade
. The base class needs to have things like DerivedClass operator++() { /* Increment and return *this */ }
inside -- specified in terms of derived in the member function signatures.
至于 CRTP,它的大多数用例是允许基类在派生类方面提供一些东西;例如 Boost 的iterator_facade
. 基类需要有像DerivedClass operator++() { /* Increment and return *this */ }
inside 之类的东西——根据成员函数签名中的派生来指定。
It can be used for polymorphic purposes, but I haven't seen too many of those.
它可以用于多态目的,但我还没有看到太多。
回答by Dan O
The link you provide mentions boost iterators as an example of static polymorphism. STL iterators also exhibit this pattern. Lets take a look at an example and consider why the authors of those types decided this pattern was appropriate:
您提供的链接提到 boost 迭代器作为静态多态性的一个例子。STL 迭代器也表现出这种模式。让我们看一个例子并思考为什么这些类型的作者认为这种模式是合适的:
#include <vector>
#include <iostream>
using namespace std;
void print_ints( vector<int> const& some_ints )
{
for( vector<int>::const_iterator i = some_ints.begin(), end = some_ints.end(); i != end; ++i )
{
cout << *i;
}
}
Now, how would we implement int vector<int>::const_iterator::operator*() const;
Can we use polymprhism for this? Well, no. What would the signature of our virtual function be? void const* operator*() const
? That's useless! The type has been erased (degraded from int to void*). Instead, the curiously recurring template pattern steps in to help us generate the iterator type. Here is a rough approximation of the iterator class we would need to implement the above:
现在,我们将如何实现int vector<int>::const_iterator::operator*() const;
我们可以为此使用 polymprhism 吗?嗯,不。我们的虚函数的签名是什么?void const* operator*() const
? 那没用!类型已被擦除(从 int 降级为 void*)。相反,奇怪的重复模板模式介入帮助我们生成迭代器类型。这是我们需要实现上述内容的迭代器类的粗略近似:
template<typename T>
class const_iterator_base
{
public:
const_iterator_base():{}
T::contained_type const& operator*() const { return Ptr(); }
T::contained_type const& operator->() const { return Ptr(); }
// increment, decrement, etc, can be implemented and forwarded to T
// ....
private:
T::contained_type const* Ptr() const { return static_cast<T>(this)->Ptr(); }
};
Traditional dynamic polymorphism could not provide the above implementation!
传统的动态多态无法提供上述实现!
A related and important term is parametric polymorphism. This allows you to implement similar APIs in, say, python that you can using the curiously recurring template pattern in C++. Hope this is helpful!
一个相关且重要的术语是参数多态性。这允许您在 Python 中实现类似的 API,您可以在 C++ 中使用奇怪的重复模板模式。希望这是有帮助的!
I think it's worth taking a stab at the source of all this complexity, and why languages like Java and C# mostly try to avoid it: type erasure! In c++ there is no useful all containing Object
type with useful information. Instead we have void*
and once you have void*
you truely have nothing! If you have an interface that decays to void*
the only way to recover is by making dangerous assumptions or keeping extra type information around.
我认为有必要探讨一下所有这些复杂性的根源,以及为什么像 Java 和 C# 这样的语言大多试图避免它:类型擦除!在 C++ 中,没有有用的 all 包含Object
具有有用信息的类型。相反,我们拥有void*
,一旦拥有,void*
您就真的一无所有!如果您有一个接口衰减到void*
恢复的唯一方法是做出危险的假设或保留额外的类型信息。
回答by cmaster - reinstate monica
While there may be cases where static polymorphism is useful (the other answers have listed a few), I would generally see it as a bad thing. Why? Because you cannot actually use a pointer to the base class anymore, you always have to provide a template argument providing the exact derived type. And in that case, you could just as well use the derived type directly. And, to put it bluntly, static polymorphism is not what object orientation is about.
虽然在某些情况下静态多态很有用(其他答案列出了一些),但我通常认为这是一件坏事。为什么?因为您实际上不能再使用指向基类的指针,所以您总是必须提供一个模板参数来提供确切的派生类型。在这种情况下,您也可以直接使用派生类型。而且,坦率地说,静态多态不是面向对象的。
The runtime difference between static and dynamic polymorphism is exactly two pointer dereferenciations (iff the compiler really inlines the dispatch method in the base class, if it doesn't for some reason, static polymorphism is slower). That's not really expensive, especially since the second lookup should virtually always hit the cache. All in all, those lookups are usually cheaper than the function call itself, and are certainly worth it to get the real flexibility provided by dynamic polymorphism.
静态和动态多态之间的运行时差异正好是两个指针解引用(如果编译器确实在基类中内联了调度方法,如果由于某种原因没有内联,静态多态会更慢)。这并不是很昂贵,特别是因为第二次查找实际上应该总是命中缓存。总而言之,这些查找通常比函数调用本身更便宜,并且为了获得动态多态性提供的真正灵活性当然是值得的。