内联导致忽略虚拟函数的模板类的专用成员函数被忽略 [英] Inlining causes specialized member function of template class overriding virtual functions to get overlooked

查看:113
本文介绍了内联导致忽略虚拟函数的模板类的专用成员函数被忽略的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想和你们分享一个奇怪的例子,我偶然发现了这个例子,这让我思考了两天.

I wanted to share a strange example with you guys that I stumbled upon and that kept me thinking for two days.

要使此示例正常工作,您需要:

For this example to work you need:

  • 三角形虚拟继承(在成员函数getAsString()上)
  • 覆盖虚拟函数的模板类(此处为Value<bool>::getAsString())的成员函数专用化
  • (自动)由编译器内联
  • triangle-shaped virtual inheritance (on member function getAsString())
  • member function specialization of a template class (here, Value<bool>::getAsString()) overriding the virtual function
  • (automatic) inlining by the compiler

您从一个模板类开始,该模板类实际上继承了一个通用接口,即一组虚拟函数.稍后,我们将专门介绍这些虚拟功能之一.内联然后可能会导致我们的规格被忽视.

You start with a template class that virtually inherits a common interface - i.e. a set of virtual functions. Later, we will specialize one of these virtual functions. Inlining may then cause our specilization to get overloooked.

// test1.cpp and test2.cpp
#include <string>

class ValueInterface_common
{
public:
  virtual ~ValueInterface_common() {}
  virtual const std::string getAsString() const=0;
};

template <class T>
class Value :
  virtual public ValueInterface_common
{
public:
  virtual ~Value() {}
  const std::string getAsString() const;
};

template <class T>
inline const std::string Value<T>::getAsString() const
{
  return std::string("other type");
}   

接下来,我们必须继承此Value类和本身也需要模板化的Parameter类中的接口:

Next, we have to inherit this Value class and the interface in a Parameter class that itself needs to be templated as well:

// test1.cpp
template <class T>
class Parameter :
  virtual public Value<T>,
  virtual public ValueInterface_common
{
public:
  virtual ~Parameter() {}
  const std::string getAsString() const;
};

template<typename T>
inline const std::string Parameter<T>::getAsString() const
{
  return Value<T>::getAsString();
}

现在,不要为类型等于bool的Value向前指定特殊化的声明.

Now, do not(!) give the forward declaration of a specialization for Value for type equaling bool ...

// NOT in: test1.cpp
template <>
const std::string Value<bool>::getAsString() const;

但是只需给出这样的定义...

But instead simply give its definition like this ...

// test2.cpp
template <>
const std::string Value<bool>::getAsString() const
{
  return std::string("bool");
}

..但在另一个模块中(很重要)!

.. but in another module (that's important)!

最后,我们有一个main()函数来测试正在发生的事情:

And finally, we have a main() function to test what is happening:

// test1.cpp
#include <iostream>

int main(int argc, char **argv)
{
  ValueInterface_common *paraminterface = new Parameter<bool>();
  Parameter<int> paramint;
  Value<int> valint;
  Value<bool> valbool;
  Parameter<bool> parambool;

  std::cout << "paramint is " << paramint.getAsString() << std::endl;
  std::cout << "parambool is " << parambool.getAsString() << std::endl;
  std::cout << "valint is " << valint.getAsString() << std::endl;
  std::cout << "valbool is " << valbool.getAsString() << std::endl;
  std::cout << "parambool as PI is " << paraminterface->getAsString() << std::endl;

  delete paraminterface;

  return 0;
}

如果按以下方式编译代码(我将其放入名为test1.cpp和test2.cpp的两个模块中,其中后者仅包含专业化和必要的声明):

If you compile the code as follows (I placed it into two modules named test1.cpp and test2.cpp where the latter only contains the specialization and necessary declarations):

g++ -O3 -g test1.cpp test2.cpp -o test && ./test

输出为

paramint is other type
parambool is other type
valint is other type
valbool is bool
parambool as PI is other type

如果使用-O0或仅使用-fno-inline进行编译-或者也确实给出了特殊化的前向声明-结果将变为:

If you compile with -O0 or just -fno-inline - or also if you do give the forward declaration of the specialization - the result becomes:

paramint is other type
parambool is bool
valint is other type
valbool is bool
parambool as PI is bool

好笑,不是吗?

到目前为止,我的解释是:内联在第一个模块(test.cpp)中起作用.所需的模板函数被实例化,但是其中一些最终被内联到对Parameter<bool>::getAsString()的调用中.另一方面,对于valbool,此操作无效,但模板已实例化并用作功能.然后,链接器会找到实例化的模板函数和第二个模块中给定的专用模板函数,并为后者确定.

My explanation so far is: Inlining is at work in the first module (test.cpp). The required template functions get instantiated but some just end up being inlined in the calls to Parameter<bool>::getAsString(). On the other hand, for valbool this did not work but the template is instantiated and used as a function. The linker then finds both the instantiated template function and the specialized one given in the second module and decides for the latter.

您怎么看?

  • 您认为此行为是错误吗?
  • 为什么内联对于Parameter<bool>::getAsString()而不对Value<bool>::getAsString()不起作用,尽管两者都覆盖了虚函数?
  • do you consider this behavior a bug?
  • Why does inlining work for Parameter<bool>::getAsString() but not for Value<bool>::getAsString() although both override a virtual function?

推荐答案

我推测您遇到了ODR问题,因此猜测为什么某些编译器优化与另一种编译器设置的行为有所不同是毫无道理的.

I speculate that you have an ODR problem, so there is little sense in guessing why some compiler optimization behaves differently from another compiler setting.

实质上,一个定义规则指出,同一实体应 在整个应用程序中具有完全相同的定义,否则 效果未定义.

In essence, the One Definition Rule states that the same entity should have the exact same definition throughout an application, otherwise the effects are undefined.

一个基本的问题是,没有看到类模板成员函数的专用版本的代码可能仍会编译,可能链接,甚至可能运行.这是因为在没有显式专业化的情况下(前向声明),将启动非专业化版本,从而可能实现对您的专业化类型也起作用的通用功能.

The fundamental problem is that the code that doesn't see the specialized version of your class template member function might still compile, is likely to link, and sometimes might even run. This is because in the absence of (a forward declaration of) the explicit specialization, the non-specialized version kicks in, likely implementing a generic functionality that works for your specialized type as well.

因此,如果幸运的话,您会收到有关缺少声明/定义的编译器错误,但是,如果您真的很不幸,则会得到工作"的代码,而不是您打算执行的操作.

So if you are lucky, you get a compiler error about missing declarations/definitions, but if you are really unlucky you get "working" code that does not what you intend it to do.

解决方法:始终包括所有模板专业化的(转发)声明.最好将它们放在单个标头中,并在所有调用您的类的客户端中包含任何可能的模板参数的标头.

The fix: always include (forward) declarations of all template specializations. It's best to put those in a single header and include that header from all clients that call your class for any possible template argument.

// my_template.hpp
#include "my_template_fwd.hpp"
#include "my_template_primary.hpp"
#include "my_template_spec_some_type.hpp" 

// my_template_fwd.hpp
template<typename> class my_template; // forward declaration of the primary template

// my_template_primary.hpp
#include "my_template_fwd.hpp"
template<typename T> class my_template { /* full definition */ };

// my_template_spec_some_type.hpp
#include "my_template_fwd.hpp"
template<> class my_template<some_type> { /* full definition */ };

// some_client_module.hpp
#include "my_template.hpp" // no ODR possible, compiler will always see unique definition

很明显,您可以通过为模板专门化创建子目录来重新组织命名,并相应地更改包含路径.

Obviously, you could reorganize the naming by making subdirectories for template specializations and change the include paths accordingly.

这篇关于内联导致忽略虚拟函数的模板类的专用成员函数被忽略的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆