6

在中等规模甚至大型复杂项目中,将模板声明和定义分开有助于减少编译时间。

然而,在复杂的代码中,小的程序员错误可能会导致不被注意的行为变化,例如调用通用版本而不是专用版本。

示例:由于缺少声明,模板特化变得不可见。

///////////////////// file  A.hpp /////////////////////

#include <iostream>

template <typename T>

class A 
{
public:
  void foo() 
  {
      std::cerr << " calling generic foo " << std::endl ;
  }
};

// forgetting following declaration leads to an unintended program behaviour
template <> void A< int >::foo();

///////////////////// file  A-foo-int.cpp /////////////////////
#include "A.hpp"

template <> 
void A< int >::foo()
{
  std::cerr << "calling  <int> version of foo" << std::endl;
}

///////////////////// file  main.cpp /////////////////////
#include "A.hpp"

int main(int argc , char** argv)
{
  A<int>* a = new A<int>();
  a->foo();
  return 0;
}

///////////////////// Makefile /////////////////////
CC = g++
CPPFLAGS += -Wall -O3
CXXFLAGS += --std=gnu++0x

all: nonrobust-template-setup

nonrobust-template-setup: main.o A-foo-int.o  
    $(CC)  $(CPPFLAGS) main.o A-foo-int.o  -o nonrobust-template-setup

clean: 
    rm -rf *.o nonrobust-template-setup

//////////////////////////////////////////

问题:是否可以进行更健壮的设置(独立于编译器和平台),如果,它会是什么样子?

如果不是,那么测试所需功能版本是否被调用的好方法是什么?

4

4 回答 4

2

您不能以这种方式分离声明和定义:如果您将专门化成员函数的定义委托在单独的.cpp文件中,无论您是否在主模板之后立即声明您的专门化,编译器都无法实例化它,并且链接器会抱怨未解决的引用。

通常,类模板的成员函数的定义放在头文件中,除非您为相应的类模板提供显式实例化:

template class X<int>; // You should add this to make your program build,
                       // or instantiate the corresponding class template
                       // specialization and invoke the foo() method in the
                       // same translation unit (A.cpp)

一般来说,除非您遇到非常可怕的编译时间问题,否则我建议您遵循常见做法并将所有内容放在一个头文件中,以供所有需要使用类模板的翻译单元包含:

///////////////////// file  A.hpp /////////////////////

#include <iostream>

template <typename T>

class A 
{
public:
    void foo() 
    {
        std::cerr << "error: called generic foo " << std::endl ;
    }
};

template <> 
void A< int >::foo()
{
    std::cerr << "calling  <int> version of foo" << std::endl;
}

///////////////////// file  main.cpp /////////////////////
#include "A.hpp"

int main(int argc , char** argv)
{
    A<int>* a = new A<int>();
    a->foo();
    return 0;
}   

如果您面临非常可怕的编译时间问题,那么您可以分离成员函数定义并将它们放入具有显式实例化的单独翻译单元中,但是在 C++11 中没有干净/简单的方法来确保您的所有专业化在.cpp主模板之后立即声明单独文件中的降级(如良好实践所建议的那样)。如果有的话,我想它会很受欢迎,你不需要来这里问它,因为每个人都面临这样的设计问题。

在某些情况下,一些花哨的宏可能会有所帮助,但令人怀疑的是,在真正复杂的项目中,它们会比维护痛苦带来更多好处。

在 C++03 标准中尝试通过引入export关键字来解决此问题,但实现经验证明编译器供应商难以支持,这就是为什么export不再成为 C++ 标准的一部分(自 C++11 起) .

希望有更好的模块解决方案能够将其纳入 C++14 并为模板设计提供解决方案。

于 2013-03-12T17:24:14.073 回答
1

我认为你能做的最好的事情是static_assert通用模板永远不会用应该专门化的类型实例化。

以下代码仅用于说明 - 我可能会使用BOOST_STATIC_ASSERTstd::is_same如果我可以使用 c++11)。基本思想是防止使用您禁止的类型集隐式实例化非专用模板。当然,如果您忘记添加静态断言和专业化,您仍然会失败。

template<class T, class U>
struct is_same { enum { value = false }; };

template<class T>
struct is_same<T, T> { enum { value = true }; };

template <bool enable>
struct StaticAsserter
{
    char test[enable];
};

template <typename T>
struct foo
{
    // Make sure we can't implicit instantiate foo for int.
    StaticAsserter<!is_same<int, T>::value> DisallowInt;
};

int main()
{
    foo<unsigned> hi;
    foo<int> fail;

    return 0;
}
于 2013-03-12T18:07:44.060 回答
0

好的,A<T>::foo()仅当您在其他地方提供了专门化时,从注释中实例化通用实现不一定是错误。

因此,您想要的是找到名称与只应在编译器目标文件的特定列表中实例化的特化重复的通用模板实例化——这减少了在两个数据集中寻找匹配字段的过程。为此,有join

# every object and where it's defined
nm -A *.o | c++filt | grep ' T ' \
| sort -k3 > @all.definitions

# definitions that shouldn't be duplicated:
nm -A A-foo-int.o | c++filt | grep ' T ' \
| sort -k3 > @my.definitions

# everything that shows on both lists:
join -j3 @my.definitions @all.definitions

编辑: grep 模式的 sed 语法并没有很好地工作。

于 2013-03-13T15:13:57.203 回答
0

确保这一点的方法是不提供通用模板的任何定义foo()。当你这样做时,不需要声明专业化:

// A.h
template <typename T> struct A { void foo(); };
// main.cc
#include "A.h"
int main ( int c, char **v )
{
    A<int>().foo();
    // A<long>().foo();  // this line will compile but not link
}
// A.cc
#include <cstdio>
#include "A.h"
template<> void A<int>::foo() { puts("foo!"); }
于 2013-03-12T19:59:06.183 回答