我正在尝试使用interface
类,我有以下class
结构:
IBase.h:
#pragma once
class IBase
{
protected:
virtual ~IBase() = default;
public:
virtual void Delete() = 0;
IBase& operator=(const IBase&) = delete;
};
IQuackable.h:
#ifndef IQUACKABLE
#define IQUACKABLE
#include "IBase.h"
#include <iostream>
class IQuackable : public IBase
{
protected:
IQuackable() = default;
~IQuackable() = default;
public:
virtual void Quack() = 0;
static IQuackable* CreateInstance();
};
#endif //
野鸭:
#pragma once
#include "IQuackable.h"
class MallardDuck : public IQuackable
{
private:
MallardDuck();
protected:
~MallardDuck();
public:
void Delete() override;
void Quack() override;
friend IQuackable* IQuackable::CreateInstance();
};
野鸭.cpp:
#include "MallardDuck.h"
MallardDuck::MallardDuck() {}
MallardDuck::~MallardDuck() {}
void MallardDuck::Delete() { delete this; }
void MallardDuck::Quack()
{
std::cout << "Quack!n";
}
IQuackable* IQuackable::CreateInstance()
{
return static_cast<IQuackable*>(new MallardDuck());
}
此外,我还创建了类 RedHeadDuck.h 和 .cpp,其声明和定义与 MallardDuck 相同。
最后,主类代码:
#include "MallardDuck.h"
#include "RedHeadDuck.h"
int main()
{
IQuackable* mallardDuck = MallardDuck::CreateInstance();
IQuackable* redHeadDuck = RedHeadDuck::CreateInstance();
mallardDuck->Quack();
redHeadDuck->Quack();
}
在这里我得到了两个错误:
LNK2005 "public: static class IQuackable
* __cdecl IQuackable::CreateInstance(void)" (?CreateInstance@IQuackable@@SAPAV1@XZ)已经在MallardDuck.obj中定义"。
LNK1169"找到一个或多个多重定义的符号"。
正如我发现的那样,问题在于双重定义,但它如何解决?
我已经阅读了有关标题守卫的信息,但是,据我了解,在这种情况下它无济于事。人们也写了内联函数,但我还没有意识到它在这里如何使用。
我能做什么?
目标
我想这些是你试图通过采用所有复杂的模式来获得的:
- 接口,即"多种类型,同一组方法">
- 某种抽象的工厂模式,即,你想要一个"实例化器",它提供一个静态方法(与全局函数没有太大区别)来调用,并返回派生类的实例
- 禁止用户直接调用派生类的 CTOR
- 通过实施
Delete()
方法处理 DTOR
要求 1-3
至少有 3 种方法可以满足要求 1-3,如下所述:
1. 派生类隐藏其基的静态方法
这是最简单的方法,它完全能够进行当前main.cpp
。派生类可以重写其基类的静态方法。
在文件MallardDuck.h
和RedHeadDuck.h
中:
// Replace this:
// friend IQuackable* IQuackable::CreateInstance();
// With this:
static IQuackable* CreateInstance();
在文件MallardDuck.cpp
(以及类似的RedHeadDuck.cpp
):
// Replace this:
// IQuackable* IQuackable::CreateInstance() {
// return static_cast<IQuackable*>(new MallardDuck());
// }
// With this:
IQuackable* MallardDuck::CreateInstance() {
return new MallardDuck();
}
这样做的问题是:其他不重写和隐藏CreateInstance()
的派生类仍将IQuackable::CreateInstance()
公开为"回退"。因此:
- 如果你没有真正实现
IQuackable::CreateInstance()
(到目前为止,你不必这样做),那么一旦通过派生类调用它,代码就不会编译,也不会给出其他人可以理解的理由;或者 - 如果你选择实现它,你最好在其中抛出一个异常,这可能会让你的用户感到惊讶;否则你将不得不返回一个
nullptr
或其他东西,这是C++中最糟糕的做法(这就是我们在 C 中所做的,因为它没有语言级别的错误处理支持;任何无法完成其工作的C++函数都不应该返回)。
无论哪种方式都不优雅。
2. 采用抽象工厂模式
这种模式需要一个合作的"工厂类",这是抽象的;然后,每当你推导出一个具体的庸医时,也导出它的工厂。
在你的情况下,你需要勾勒出一个IQuackableFactory
,暴露IQuackableFactory::CreateInstance()
,然后推导出一个MallardDuckFactory
和一个RedHeadDuckFactory
。
已经有很多很好的例子了,所以我不会在这里演示。
3. 使用 CRTP 进行特征注入
还有另一种做事方式。通过CreateInstance()
,您实际上提供了一个"给我一个此类的实例!"功能。通常,我们使用CRTP(奇怪的重复模板模式)将某个功能"注入"到类中。
首先将此文件写入EnableCreateInstance.hpp
:
#ifndef _ENABLECREATEINSTANCE_HPP_
#define _ENABLECREATEINSTANCE_HPP_
template <class T>
struct EnableCreateInstance {
static T* CreateInstance() { return new T(); }
};
#endif //_ENABLECREATEINSTANCE_HPP_
然后在MallardDuck.h
:
// Add:
#include "EnableCreateInstance.hpp"
class MallardDuck : public IQuackable, public EnableCreateInstance<MallardDuck> {
private:
MallardDuck();
friend class EnableCreateInstance<MallardDuck>;
...
在文件RedHeadDuck.h
中执行类似的操作:包含标头,公开继承EnableCreateInstance<RedHeadDuck>
,并将EnableCreateInstance<RedHeadDuck>
声明为友元类。
这提供了更大的灵活性:您仍然提供接口CreateInstance()
,但以一种不那么"激进"的方式:派生类可以自由选择是否提供CreateInstance()
。如果他们这样做,只需继承并(如果 ctor 是私人的)宣布友谊;如果没有,请省略其他继承。
要求 4
好吧,实际上您可以在非静态非dtor方法中使用delete this
。但:
- 您必须确保(这可能很困难)不再访问已删除对象的数据成员或虚函数,否则会导致未定义的行为;
- 您给用户留下了指向已删除对象的悬空指针。
因此,我们很少在现代C++中提供这样的"删除器"。您可以通过智能指针获得它可能提供的所有好处,以及避免 UB 的能力等等。