在C++中模拟虚构造函数

4
在我的应用程序中,我必须从一个基类派生一些类,问题是我想要强制派生类实现3个特定的构造函数。由于c++没有虚纯构造函数,这似乎非常绝望(我不得不手动检查每个类的实现以确保特定的构造函数被实现,这并不好玩)。
昨天我发现了一种疯狂的方法来模拟虚构造函数的行为:


template <class T>
class AbstractEnforcer{
  protected:
    AbstractEnforcer(){}
  private:
    static void Enforcer(){
      delete new T();
      delete new T(*(new unsigned int));
      delete new T(*(new unsigned int, *(new QString));
    }
  }
class AbstractClass : private AbstractEnforcer<AbstractClass>{
}
这种方法的唯一不便之处在于我必须使用以下语法声明所有派生类:

class X : private AbstractEnforcer<X>

即使这不是一个问题; 因为Enforcer()方法从未被调用(即使它什么也不做[希望如此!])

我的问题是: “有没有一种方法(不使用宏),可以强制派生类使用此机制,而无需将AbstractClass参数化(因为这只适用于一级派生)?”


template <class T>
class AbstractClass : private AbstractEnforcer<T>{

}
6个回答

4

你的解决方案并不能解决这个问题,因为没有被使用的模板代码不会被实例化,所以除非你手动调用该函数,否则它不会验证所需构造函数的存在。

你可以在执行器的构造函数中调用这个方法:

template <class T>
class AbstractEnforcer{
    protected:
        AbstractEnforcer(){ Enforcer(); }
    private:
        static void Enforcer(){
            delete new T();
            delete new T(*(new unsigned int));
            delete new T(*(new unsigned int, *(new QString)));
        }
        // disable:
        AbstractEnforcer (const AbstractEnforcer &enf);
};

class AbstractClass : private AbstractEnforcer<AbstractClass>{

};

int main () {
    AbstractClass c;
}

接着,编译器就会抱怨——任务完成了。

请注意,我已经禁用了复制构造函数,以便没有办法绕过检查(通过调用其他构造函数)。

编辑-非泄漏Enforcer(): [因为在那里绝对没有必要使用动态分配。]

        static void Enforcer(){
            T t1();
            T t2(int(3));
            T t3(int(4), QString());
        }

实际上,我在另一个cpp文件中定义了执行函数,并且静态构造函数检查工作得非常好,但我承认这是更好的方法(我正在使用gcc-4.x),并且不知道这是否适用于其他编译器。 - chedi
我完全忘记告诉你,如果你在构造函数中调用Enforcer方法,你会创建一个无限循环和堆栈溢出(嘿嘿)。因为Enforcer方法的第一条指令是调用默认构造函数,这将再次调用Enforcer方法,以此类推。 - chedi
我明白了。那么,您可以使用静态布尔标志来改变RAII的使用方式,这样您可以在第一次进入时设置它,在离开时取消设置,并且只有在未设置时才尝试这些操作..然后您就可以解决递归问题了。另外,另一个建议是使用自动变量调用所需的构造函数,而不是使用动态堆分配。 - rmn
一个小细节:你的代码调用了Enforcer(),它会泄漏整型和QString。 - Joh
Joh - 谢谢提醒。但我之前已经说过,在那个地方没有必要使用动态分配。现在已经进行了编辑,谢谢。 - rmn
显示剩余3条评论

3
请参见C++ FAQ中的此页面
我的建议是这样的:
class AbstractClass {
    public:
        // 创建一个新对象
        virtual AbstractClass* create() const = 0;
        // 使用无符号整数创建一个新对象
        virtual AbstractClass* create(unsigned int) const = 0;
        // 使用无符号整数和字符串创建一个新对象
        virtual AbstractClass* create(unsigned int, QString) const = 0;
};
然后每个派生类都需要重写这些函数,以使用不同的构造函数创建新对象。

我知道我们不能有虚构造函数,也不想有一个,我的目的是编译器静态代码检查,如果我忘记实现特定构造函数原型,它会提醒我。我的项目是一种插件式动态加载系统,我需要以某种方式强制执行第三方代码的构造函数原型实现。 - chedi
2
@chedi:我不明白你在这里试图实现什么。为什么构造函数需要那三个参数?提供一个工厂插件,接受相同的参数似乎更合理(常见和惯用),这与jhoyt发布的内容接近。你打算如何在代码中实例化一个在编译时未知类型的对象? - David Rodríguez - dribeas
我支持dribeas的评论。您的想法行不通,因为插件主机无法构造编译时未知的对象。您需要一个工厂(例如Object* CreateObject(...)函数列表,插件必须导出它们)。另外,通过在导出项上要求C调用约定(返回的对象可以是C++),您将使自己的生活更加轻松,C ++插件很棘手。 - Frunsi

1

我可能会为生成测试而创建一个模板:

template <typename T>
void enforceConstructors() {
    T t1;
    T t2(0);
    QString q;
    T t3(0, q);
}

然后在你的测试中的某个地方,执行以下操作:

enforceConstructors<X>();
enforceConstructors<Y>();
enforceConstructors<Z>();

这些可能会在类X、Y、Z的各个位置一起出现,也可能分别出现。这取决于您想如何组织测试。

如果我使用的值不合适,请输入一些合适的值,或者编译该测试但不运行它。如果您没有单元测试,请获取一些,或者将以下内容添加到类中(而不是从基类继承):

#ifndef NDEBUG
    static void test() { enforceConstructors<X>(); }
#endif

通常情况下,您不需要将构造函数作为抽象基类定义的接口的一部分。原因是这样的接口是用于动态多态性 - 您将对象传递给某个函数,并在其上调用函数。您无法将类“传递”到函数并使其实例化类,除非使用模板。模板大多在编译时强制执行其接口 - 如果您实例化模板并且它使用构造函数,则必须存在构造函数。

你可能想要在示例中添加对Y和Z类的强制执行,以展示如何扩展您想要的内容。此外,我建议将static void test() 转换为非静态且更不常见的类,例如: *extern void constructorEnforcer()*。这是为了防止编译器优化掉文件中未使用的静态代码。 - NVRAM
无论编译器是否优化它,都必须确保它是合法的,因为它是一个真实的函数。至少,在C++中从另一个函数调用未定义的函数需要进行诊断(模板代码是该规则的例外,只要它没有被实例化并且被调用的函数依赖于模板参数)。在这里,即使test()在以后的某个时候被删除为死代码,模板也必须被实例化。C++不允许死代码不被编译,只允许稍后删除。 - Steve Jessop
想一想,这段代码确保相关的构造函数被声明。如果构造函数被声明但未定义,则此代码可能无法检测到该问题,如果您从未链接任何实际执行测试的代码。我不确定我们应该做什么 - 无论如何,最好是找出一些有效的参数并运行测试。 - Steve Jessop

1

从你对其中一个答案的评论中,我认为你并不是真正想要在这里询问的东西,而是另一件事。我所指的评论是:

第一部分:

我知道我们不能有虚构造函数,我也不想有一个,我的目的是编译器静态代码检查,如果我忘记实现特定的构造函数原型,它会提醒我。

第二部分:

我的项目是一个插件式的动态加载系统,我必须以某种方式强制执行第三方代码的构造函数原型实现。

你在问题中所询问的是1,你可以用不同的方式来强制执行它,只需阅读一些答案,或查看元编程示例和boost::type_traits库。

现在,如果你真正想要的是第二部分:提供动态加载插件机制,那么你不需要强制构造函数,但需要为插件对象和插件对象的创建提供一个公共接口。在编译时无法实例化未知对象的实例,这意味着你将无法从代码中调用构造函数。我建议
// Interface:
class plugin {
public:
   virtual void operation() = 0;
};
class plugin_factory {
public:
   virtual plugin* create() = 0;
   virtual plugin* create( unsigned int ) = 0;
   virtual plugin* create( unsigned int, QString const & ) = 0;
};

用户需要提供插件实现和创建插件的工厂。他们可能需要为他们的库实现一个入口点,以便您可以访问工厂(或者他们可以在您的系统中注册他们的工厂),否则我建议使用一个库来实现这些目的(boost::extension似乎是一个值得考虑的地方)。

0

我最终采用了这个解决方案,但并不完全信服:


#ifdef NDEBUG

#ifndef ENFORCE_CTORS
#define ENFORCE_CTORS(enforcingTemplate, enforcedClass) \
  friend void enforcingCtors(){static enforcingTemplate<enforcedClass> _enforcer;}
#endif


template<class T>
      class AbstractEnforcer : T{
        public:
          explicit AbstractEnforcer(){
            T enforcedCtor0(                                     );
            T enforcedCtor1( *(new unsigned int)                 );
            T enforcedCtor2( *(new unsigned int), *(new QString) );
            T enforcedCtor3( *(new unsigned int), *(new float  ) );
          }
      };
#endif

而在我想要强制执行的每个类中,我只需像这样添加:



class X{
  ENFORCE_CTORS(AbstractEnforcer, X);
  /*
    .....
  */
}

我没有找到其他将此代码动态注入类中的方法。而且,我可能对操作的最终目的表述不清(对我的糟糕英语感到抱歉)。


我只是保留了*(新 typename)参数样式表示来显式地说明参数类型,因为实际上代码执行仅在项目的调试阶段完成,并且在发布周期中该代码会被简单地忽略。 - chedi

0

如果你忘记实现构造函数但在代码中使用它,你将会得到一个编译错误。例如:

Base * p = new Derived( 42 );

如果没有提供Derived(int)构造函数,则会在编译时出现错误 - 不会使用Base构造函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接