模板类中实现的分离

Separation of implementation in template classes

本文关键字:分离 实现      更新时间:2023-10-16

我有以下问题:

假设存在一个基类CCD_ 1。

这个类有很大一部分总是一样的。小部件应该是通用的,例如具有模板参数T

目前,我不想创建A的模板类,因为我不想在头文件中有大型非通用部件实现。我能用它做什么?我有没有可能实现与类头分离的大的非泛型部分,但也有小的泛型部分作为某种模板?

举个例子:假设您有一个通用树,它有一些模板函数(如add(T o)),但许多函数不依赖于模板(如size(),但更复杂)。

作为补充:A有多个子类,这些子类应该决定T的最终类型…

我知道编译器无法处理cpp文件中模板的实现。但是,有没有一种概念上的方法来解决分离?例如,与模板不同的东西?

直接处理分离的一种方法是将模板拆分为两个头文件(这与将非模板内容拆分为.h声明和.cpp实现的方式非常相似)。

然后,您可以在模板上使用显式模板实例化。

  // tree.h
  // Included by anything that needs to use the tree template.
  template <typename T> class tree {
     void inline_func() { }
     void noninline_func();
  };
  // tree_impl.h
  // This header is only included by a single .cpp file: the
  // template repository module that contains instantiations.
  template <typename T> tree<T>::noninline_func()
  {
  }
  // template-repo.cpp
  #include "tree.h"
  #include "tree_impl.h" // only template-repo.cpp includes this!
  // same for all other templates
  #include "mytype.h"
  #include "othertype.h"
  template class tree<mytype>; // instantiate template here over mytype
  template class tree<othertype>; // ditto over othertype
  // ... other instantiations

您可以使用预处理器将两个头文件滚动在一起:

  // tree.h
  template <typename T> class tree {
     void inline_func() { }
     void noninline_func();
  };
  #ifdef INSTANTIATING_TEMPLATES
  template <typename T> tree<T>::noninline_func()
  {
  }
  #endif
  // template-repo-cpp
  #define INSTANTIATING_TEMPLATES
  #include "tree.h"
  #include "mytype.h"
  template class tree<mytype>;

然而,这种轻微的便利意味着,当你在任何地方A0时,你会通过预处理器传递很多额外的cruft。

这一切都有缺点,就像C++一样。它不仅是手动维护,而且每次您触摸模板头或用作模板参数的某个类型时,都必须重新编译整个template-repo.cpp。(当然,事情可以分为多个迷你回购:解决解决方法问题的对策)。

但是,难道没有一种概念性的方法来解决分离吗?例如与模板不同的东西?

如果你正在寻找一种可移植的c++替代模板,那么它们根本无法提供模板所能提供的强大功能。除此之外,让我们试着解决你的问题。

您可以简单地将函数打包到一个单独的类中,该类可以在任何其他头中实现。我们有两种选择,一种是将代理类作为基类的成员添加,另一种是使用继承。

选择A(直接包括作为会员)

使用这种方法你会遇到一个问题,他们无法直接访问函数。不能使.运算符过载!处理此问题的一种方法是使用代理函数,该函数只返回对成员变量的引用。下面是一个最小的工作示例:

#include <iostream>
using namespace std;
// forward declaration 
template <typename T> 
class B;
template <typename T>
class A {
public:
    // allows friendship regardless of the type instantiated with class A
    template <typename U>
    friend class B;
    A(int a) : a_(a), funcs_(this) {}
    // this could be named anything
    B<T>& utils() { return funcs_; }
private:
    int a_;
    // proxy class instance, should probably be static 
    B<T> funcs_;
};
// this class could be modifed to accept any type but i did this for simplicity 
template <typename T> 
class B {
public:
    explicit B(A<T>* base) : base_(base) {}
    // function implementations go here...
    void print() { std::cout << base_->a_; }
private:
    A<T>* base_;
};

int main() {
    A<int> testA(23);
    testA.utils().print();
    return 0;
}

选择B(继承)

使用这种方法,您将能够直接访问函数,但这是以使用继承为代价的,我知道有些人会倾向于不使用继承而不是选择A。下面是一个工作示例:

#include <iostream>
using namespace std;
// forward declaration 
template <typename T> 
class B;
template <typename T>
class A : public B<T> {
public:
    // allows friendship regardless of the type instantiated with class A
    template <typename U>
    friend class B;
    A(int a) : a_(a), B(this) {}
private:
    int a_;
};
// this class could be modifed to accept any type but i did this for simplicity 
template <typename T> 
class B {
public:
    explicit B(A<T>* base) : base_(base) {}
    // function implementations go here...
    void print() { std::cout << base_->a_; }
private:
    // pointer to base class 
    A<T>* base_; 
};

int main() {
    A<int> testA(23);
    testA.print();
    return 0;
}

如果是我的选择,我很可能会使用选项B,我不想让客户端显式调用一个函数,也不想调用另一个函数。很明显,您可以使用一些涉及typedefbind的技巧,但我认为这会增加不必要的开销。