【问题标题】:Function Template: Access Child Class' Overloaded Function In Base Class函数模板:在基类中访问子类的重载函数
【发布时间】:2013-08-22 09:58:14
【问题描述】:

我有一个基类First 和一个派生类Second。在基类中有一个成员函数create 和一个虚函数run。在Second的构造函数中,我想调用函数First::create,它需要访问其子类'run()函数的实现。一位同事建议使用函数模板,因为First 无法明确知道它是子类。听起来怪怪的?这是一些代码:

First.h

#pragma once
#include <boost/thread/thread.hpp>
#include <boost/chrono/chrono.hpp>

class First
{
public:
    First();
    ~First();

    virtual void run() = 0;
    boost::thread* m_Thread;
    void create();

    template< class ChildClass >
    void create()
    {
        First::m_Thread = new boost::thread(
            boost::bind( &ChildClass::run , this ) );
    }
};

First.cpp

#include "First.h"

First::First() {}
First::~First() {}

Second.h

#pragma once
#include "first.h"

class Second : public First
{
public:
    Second();
    ~Second();

    void run();
};

Second.cpp

#include "Second.h"
Second::Second()
{
    First::create<Second>();
}

void Second::run()
{
    doSomething();
}

我在First::create&lt;Second&gt;(); 收到一个错误,说错误:不允许类型名称。那么这个错误的原因是什么?我想我还没有完全了解模板的全部机制 - 我对这个主题很陌生。

【问题讨论】:

  • 把函数模板的定义放在表头。见,例如this question
  • 只需 template &lt;class ChildClass&gt; 进入 First.h?不幸的是,对错误没有任何影响。
  • 不,整个函数体。它需要在头文件中,因为函数模板实例化的定义需要在使用它们的每个 TU 中可用。
  • 好的,我将整个模板...创建函数体放入 First.h,但它仍然在 Second.cpp 中的 First::create&lt;Second&gt;(); 处显示该错误
  • 请同时显示标题。 SSCCE 将不胜感激。

标签: c++ templates


【解决方案1】:

虽然您可以按照 Kerrek SB 和 Arne Mertz 的“建议”使用 CRTP,但这里有一个使用成员函数模板的解决方案:

class First
{
public:
    First();
    ~First();

    virtual void run() = 0;
    boost::thread* m_Thread;

    // vvvvvvvvvvv this declares a non-template member function `create`
    void create(); // (better remove it)

    // vvvvvvvvvv this declares a member function template `create`
    template< class ChildClass >
    void create();
};

对于模板,您应该在头文件中提供定义,因为该定义必须在需要它的每个 TU 中都可用(而不仅仅是一个)。

最简单的方法是在类本身中提供成员函数模板或类模板成员的定义:

class First
{
public:
    /* ... */

    template< class ChildClass >
    void create()
    {
        // AFAIK, the `bind` is not required
        First::m_Thread = new boost::thread(&ChildClass::run,
                                            static_cast<ChildClass*>(this));
    }
};

现在,如果您想在 Second 类中调用此成员函数模板,则必须显式提供模板参数:

void Second::any_function()
{
    create<Second>();
}

您的代码存在其他一些(可能的)问题:

  • 您没有虚拟 dtor,因此删除指向基类子对象的指针将调用 UB。
  • 需要有一个低调,因为&amp;ChildClass::run 的类型是void (Second::*)()
  • createSecond 的 ctor 内部调用,它将调用 Second::run(或您提供的任何模板参数),该函数的最终覆盖在类 Second 中。如果您在派生自 Second 的类中覆盖该函数,则不会在 Second 的 ctor 中调用该覆盖。
  • First 的 dtor 应该是 detachjoin 提升线程,否则如果调用 dtor 时线程仍在运行,则将调用 std::terminate
  • run 在此示例中不必是虚拟的,完全相同的代码可以在 run 是虚拟的甚至在 First 中声明的情况下工作。

【讨论】:

  • 现在可以使用了。非常感谢您(和其他人)的时间和解释,非常感谢!
【解决方案2】:

首先您必须将模板定义放在标题中,否则链接器将无法找到实例化的模板。 第二个: 现在双关语结束了,我假设你从First&lt;Second&gt; 派生了Second。这被称为“奇怪重复的模板模式”(CRTP)。

由于基类已经是特化的,并且派生类的类型已经刻在它的类型中,所以没有必要将它的任何函数作为模板(你没有),基类的函数可以刚刚调用,没有指定模板参数:

template <class SubClass>
struct First {
  void create() {
    SubClass& sub = static_cast<SubClass&>(*this);
    mThread = make_unique<std::thread>( [&](){sub.run();} ); 
  }
};

struct Second : First<Second> {
  Second() { create(); }
  void run();
};

一些注释:

  • 您无需限定First 内的访问权限
  • 你可以直接调用create,它是一个继承的方法,由于Second不是模板,所以不需要添加限定或帮助编译器进行查找。
  • 更喜欢std::thread 而不是boost::thread,它已经成为标准有一段时间了。
  • 更喜欢 std::unique_ptr 而不是拥有所有权的原始指针
  • 我更喜欢 lambdas 而不是 bind - 我读起来更清楚。
  • 我创建了两个类struct,是为了让成员函数默认公开,并在示例代码中节省一行。除了这一点,与class 没有区别。

【讨论】:

  • 非常感谢您的回答。可悲的是,我不得不使用 boost::thread。我必须管理我还不能将您的答案应用于我的代码。 ;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-19
  • 1970-01-01
  • 2017-12-16
  • 2014-08-01
  • 1970-01-01
  • 2011-02-12
  • 1970-01-01
相关资源
最近更新 更多