2

所以我试图将一些实现接口*的类中的一些通用代码移到抽象基类中。但是,抽象基类需要了解一些派生类想要如何做事的信息,以便准确地确定要做什么。但是,我不完全确定是否应该使用纯虚函数或受保护的成员变量来实现它。

我将举一个简单的例子来描述我正在尝试做的事情。

界面:

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 也是如此。

我知道有一些与虚拟方法相关的开销(可能微不足道,但仍然如此),并且受保护的变量可能不适合封装,或者它可能被遗忘并未初始化。所以我的问题基本上是,其中哪一个是可取的,为什么,或者我应该完全避免这种情况并尝试以不同的方式处理它?

注意:我的实际代码有点复杂。我还没有实际测试过这段代码是否有效,如果不正确请见谅。

*当我说接口时,我的意思是一个只包含纯虚函数的类。

4

1 回答 1

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在每个衍生产品中重写此方法,但这没关系,因为这是(强)合同(纯虚拟方法)的一部分,您也永远不能弄乱它。

总而言之,如您所见,这两种方法是相互排斥的,只需将它们的优缺点应用于您的特定情况,就很容易决定使用哪一种。

于 2013-04-07T02:29:15.937 回答