【问题标题】:avoid constructor duplication in template specialization避免模​​板特化中的构造函数重复
【发布时间】:2014-05-06 18:02:19
【问题描述】:

假设我有一个基类,它存储了对一些 class Bar 的引用:

class FooBase
{
public:
  FooBase( Bar &ctx ) : _barCtx( ctx ) {};
  virtual ~FooBase() {};

  // Some other functions

protected:
  Bar &_barCtx;
};

我想做的是在此之上添加一个继承级别,class Foo<T> 将添加一些功能。

template< typename T >
class Foo : public FooBase
{
public:
  Foo( Bar &ctx ) : FooBase( ctx ) {};
  bool doSomething( int a );
};

然后,有一些Foo&lt;T&gt; 的实例需要提供不同版本的doSomething(),因此使用模板特化。问题是,在Foo&lt;&gt; 的每个专用版本中,我必须重新实现构造函数并将Bar 的引用传递给超类。这基本上是复制和粘贴代码,我想避免这种情况。

class Baz;

template<>
class Foo<Baz> : public FooBase
{
public:
  Foob( Bar &ctx ) : FooBase( ctx ) {};
  bool doSomething( std::string &str, int x, float g );
};

本练习的重点是提供具有不同签名的不同类型的doSomething()。那么,不使用 C++11(因为我被困在 GCC 4.6.3 上),有没有办法避免这种代码重复?或者,有没有更好的方法来提供不同的doSomething()

【问题讨论】:

  • 我想更好地了解您的问题。在这里解决doSomething 的不同签名有什么问题(在这种情况下,“它看起来不像鸭子”,就像使用模板时通常那样)。
  • 是您唯一要避免重复构造函数的那一点吗?我问的原因是我认为你可以使用 CRTP,并在 FooBase 之上创建一个类,但额外的复杂性可能不值得。
  • 是否可以将doSomething 设为非成员函数?那将绕过这个问题。 (显然,创建一个也称为doSomething 的成员调用免费的doSomething 是微不足道的)
  • @MooingDuck:这里的问题是 FooBase 有一个对 Bar 的引用,这是 doSomething() 需要的。如果我要传递 Bar,它会添加到我试图避免的参数列表中。真正的问题在doSomething()中有更多的参数,我上面贴的是它的简化版本。
  • @MarkP 你似乎试图避免一些通常不会在这里避免的事情。

标签: c++ templates template-specialization


【解决方案1】:

我实际上认为 SFINAE 方法更好,但如果由于某种原因这对您不起作用,那么专门化类模板的各个成员函数可能对您有用。但是,您必须在通用模板中声明所有重载,然后根据需要提供定义。这将确保在调用错误的重载时会出现链接错误。

另一种选择是使用 CRTP。该方法如下所示。

成员专业化方法:

#include <string>

class Bar {};

class FooBase
{
public:
  FooBase( Bar &ctx ) : _barCtx( ctx ) {};
  virtual ~FooBase() {};
protected:
  Bar &_barCtx;
};

template< typename T >
class Foo : public FooBase
{
public:
  Foo( Bar &ctx ) : FooBase( ctx ) {};
  bool doSomething( int a ) { return true; }
  // Declared, but not defined.
  bool doSomething( std::string &str, int x, float g );
};

class Baz {};

// Declared, but not defined.
template <>
bool
Foo<Baz>::doSomething(int i);

template <>
bool
Foo<Baz>::doSomething(std::string &str, int x, float g) {
    return true;
}

int main() {
    Bar b;
    Foo<int> f1(b);
    std::string s;

    f1.doSomething(1); // Compiles.
    // f1.doSomething(s, 1, 3.14f); // Link error.

    Foo<Baz> f2(b);
    // f2.doSomething(1); // Link error.
    f2.doSomething(s, 1, 3.14f); // Compiles.
}

CRTP 方法:

#include <string>

class Bar {};
class Baz {};

template <typename T>
class Spec {
    public:
        bool doSomething( int a );
};

template <>
class Spec<Baz> {
    public:
        bool doSomething( std::string &str, int x, float g );
};

class FooBase {
    public:
        FooBase( Bar &ctx ) : _barCtx( ctx ) {};
        virtual ~FooBase() {};
    protected:
        Bar &_barCtx;
};

template< typename T >
class Foo : public FooBase, public Spec<T> {
    public:
        Foo( Bar &ctx ) : FooBase( ctx ) {};
};

template <typename T>
bool Spec<T>::doSomething( int a ) {
    Foo<T> *fp = static_cast<Foo<T> *>(this);
    return true;
}

bool Spec<Baz>::doSomething( std::string &str, int x, float g ) {
    Foo<Baz> *fp = static_cast<Foo<Baz> *>(this);
    return true;
}

int main() {

    Bar b;
    std::string s;

    Foo<int> f1(b);
    f1.doSomething(1);

    Foo<Baz> f2(b);
    f2.doSomething(s, 1, 3.14f);
}

【讨论】:

  • GCC 然后指出错误:error: template-id 'doSomething&lt;&gt;' for 'bool Foo&lt;Baz&gt;::doSomething(std::string &amp;, int, float)' does not match any template declaration。如果我理解正确,即使我重载 doSomething 以具有不同的签名,编译器也无法匹配两者?
  • @MarkP:啊,我现在知道你实际上想更改 doSomething 的签名。
  • @MarkP:你能在模板的通用版本中同时拥有这两个重载吗?
  • 那个函数需要一个返回类型
  • @MarkP:是的。如果您的目标只是节省复制该单个 ctor,那么我认为很难找到一个不涉及更多代码的解决方案。另一种选择是使用 CRTP 来分解专业化。
【解决方案2】:

您可以提供每个重载,然后使用 SFINAE 启用相关重载,而不是专门化 Foo

template< typename T >
class Foo : public FooBase
{
public:
  Foo( Bar &ctx ) : FooBase( ctx ) {};

  template<
    typename U = T,
    typename = typename std::enable_if<!std::is_same<U, Baz>::value>::type>
  bool doSomething( int a )
  {
      std::cout << "doSomething( int a )\n";
  }

  template<
      typename U = T,
      typename = typename std::enable_if<std::is_same<U, Baz>::value>::type>
  bool doSomething( std::string &str, int x, float g )
  {
      std::cout << "doSomething( std::string &str, int x, float g )\n";
  }
};

(由于不能使用 C++11,请将 std::enable_ifstd::is_same 替换为 boost 版本或您自己的版本。)

【讨论】:

  • 实际上...这需要 C++11 才能工作(即使替换了 C++11 标准库的东西)。我现在正在寻找解决方法。
  • 即便如此,这确实适用于 gcc 4.6 中的 C++11 功能。 Live Example.
【解决方案3】:

这似乎真的是使用模板专业化的错误地方。模板化类型没有在声明中的任何地方使用,因此它完全是任意的。

我建议使用其他技术

1) 为您的输入定义一个抽象基类型,并让 doSomething 接受它的任何实现。

bool doSomething(DoSomethingParamsBase* params);

2) 使用枚举的 MODE 参数和后面的可变参数

bool doSomething(MODE mode...);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-12
    • 2011-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-08
    • 2016-12-04
    • 2011-02-09
    相关资源
    最近更新 更多