所以我试图在一些实现接口*的类中将一些公共代码移动到抽象基类。但是,抽象基类需要了解派生类如何执行操作以确定要执行的操作。但是,我不完全确定我是否应该使用纯虚函数或受保护的成员变量来实现它。
我将举一个简单的例子来描述我正在尝试做的事情。
界面:
class SomeInterface
{
public:
void DoSomething() = 0;
// ...
};
我正在尝试使用纯虚函数实现的抽象基类:
class AbstractBase : public SomeInterface
{
public:
virtual void DoSomething()
{
for (int i = 0; i < GetNumIterations(); i++)
{
// Call implementation in derived classes, for example
DoSomethingImpl();
}
}
protected:
virtual void DoSomethingImpl() = 0;
virtual int GetNumIterations() = 0;
};
派生类:
class Derived1 : public AbstractBase
{
protected:
virtual void DoSomethingImpl()
{
// Do actual work.
}
virtual int GetNumIterations()
{
return 5;
}
};
另一个派生类:
class Derived2 : public AbstractBase
{
protected:
virtual void DoSomethingImpl()
{
// Do actual work.
}
virtual int GetNumIterations()
{
return 1;
}
};
或者另一种方式是使用受保护的变量:
class AbstractBase
{
public:
virtual void DoSomething()
{
for (int i = 0; i < numIterations; i++)
{
// Call implementation in derived classes, for example
DoSomethingImpl();
}
}
protected:
virtual void DoSomethingImpl() = 0;
int numIterations;
};
派生的就像:
class Derived1 : public AbstractBase
{
public:
Derived1()
: numIterations(5)
{
}
protected:
virtual void DoSomethingImpl()
{
// Do actual work.
}
};
Derived2也是如此。
我知道有一些与虚拟方法相关的开销(可能无关紧要,但仍然存在),并且受保护的变量可能不适合封装,或者它可能被遗忘并且未初始化。所以我的问题基本上是,哪些是可取的,为什么,或者我应该完全避免这种情况并尝试以不同的方式处理它?</ p>
注意:我的实际代码有点复杂。我还没有真正测试过看看这段代码是否有效,所以请原谅我这是不正确的。
*当我说接口时,我指的是一个只包含纯虚函数的类。
答案 0 :(得分:2)
事实上,你要做的事情非常普遍。但是,还有一种方法可以实现这一点,明确定义AbstractBase
衍生物的契约。修改你的例子看起来如下:
class AbstractBase : public SomeInterface
{
public:
explicit AbstractBase(int numIterations) : numIterations(numIterations) {}
virtual void DoSomething()
{
for (int i = 0; i < numIterations; i++)
{
// Call implementation in derived classes, for example
DoSomethingImpl();
}
}
protected:
virtual void DoSomethingImpl() = 0;
// Can omit it, if not needed by derivatives
int GetNumIterations() { return numIterations; }
private:
int numIterations;
};
class Derived1 : public AbstractBase
{
public:
Derived1() : AbstractBase(5) {}
protected:
virtual void DoSomethingImpl()
{
// Do actual work.
}
};
class Derived2 : public AbstractBase
{
public:
Derived2() : AbstractBase(1) {}
protected:
virtual void DoSomethingImpl()
{
// Do actual work.
}
};
正如你现在可能已经理解的那样,通过契约我的意思是构造函数现在明确强制AbstractBase
的派生词正确地初始化它,所以你永远不会弄乱它。这种方法的缺点是它引入了额外的字段,如果Derived1
从未在您的情况中发生变化,那么可能会在众多的5
副本中重复这些字段。所以,如果你关心内存占用,那么我不会去寻找这个。但是,如果numIterations
可以更改,那么这种方法肯定是3个提议的方法中最好的。您所需要做的就是为AbstractBase
添加适当的设置器。
注意:我的方法更安全,更好地替代第二个方法,因为它完全解决了您提到的问题,即封装漏洞(冗余暴露实施细节)和合同弱点(当您可能忘记初始化numIterations
因为您没有被迫)。因此,不想要在当前情况下使用第二种方法。
您提出的第一种方法也很好。它的优势超过我的是它不会引入任何内存开销。只要“迭代次数”没有改变,您就不需要引入一个字段来存储它。因此,您必须在每个派生词中覆盖此GetNumIterations
方法,但它没关系,因为这是(强)合同(纯虚方法)的一部分,您也永远不会弄乱它。
总而言之,正如您所看到的,这两种方法是相互排斥的,通过简单地将它们的优缺点应用于您的特定情况,可以很容易地决定使用哪种方法。