访问超类类型成员对象的受保护成员 - 一种优雅的解决方案

4
首先,我知道我做不到这一点,我认为这不是重复的问题(这个这个问题处理的是相同的问题,但他们只想解释为什么它不起作用)。
因此,我有一个类和继承的类似概念,并且我想以某种优雅的方式做一些被禁止的事情。下面是一个非常简单的代码片段,反映了我想要做的事情:
#include <iostream>

class A{
protected:
    int var;
    std::vector <double> heavyVar;
public:
    A() {var=1;}
    virtual ~A() {}
    virtual void func() {
        std::cout << "Default behavior" << this->var << std::endl;
    }

    // somewhere along the way, heavyVar is filled with a lot of stuff
};

class B: public A{
protected:
    A* myA;
public:
    B(A &a) : A() {
        this->myA = &a;
        this->var = this->myA->var;
             // copy some simple data, e.g. flags
             // but don't copy a heavy vector variable
    }
    virtual ~B() {}
    virtual void func() {
        this->myA->func();
        std::cout << "This class is a decorator interface only" << std::endl;
    }
};

class C: public B{
private:
    int lotsOfCalc(const std::vector <double> &hv){
        // do some calculations with the vector contents
    }
public:
    C(A &a) : B(a) {
        // the actual decorator
    }
    virtual ~C() {}
    virtual void func() {
        B::func(); // base functionality
        int heavyCalc = lotsOfCalc(this->myA->heavyVar); // illegal
            // here, I actually access a heavy object (not int), and thus
            // would not like to copy it
        std::cout << "Expanded functionality " << heavyCalc << std::endl;
    }
};

int main(void){
    A a;
    B b(a);
    C c(a);
    a.func();
    b.func();
    c.func();
    return 0;
}

这样做的原因是我实际上正在尝试实现装饰器模式class B具有我想要装饰的myA内部变量),但我也想在进行“装饰”计算时使用class A的某些受保护成员(在class B及其所有子类中)。因此,这个例子不是一个正确的装饰器示例(甚至不是一个简单的示例)。在这个例子中,我只关注演示问题功能(我想使用但无法使用的功能)。甚至没有使用实现装饰器模式所需的所有类/接口(我没有一个抽象基类接口,由具体基类实例继承,以及一个抽象装饰器接口,用作具体装饰器的超类)。我只提到装饰器来说明上下文(我想要一个A*指针的原因)。

在这种情况下,我认为将(相当于)int var公开(甚至编写可公开访问的getter)没有太多意义,原因如下:

  • 更明显的一个原因是,我不希望用户直接使用信息(我有一些函数返回与/或写在我的protected变量中的相关信息,但不包括变量值本身)
  • 在我的情况下,protected变量比int更难以复制(它是一个2D的std::vector,包含double),将其复制到派生类的实例中会浪费时间和内存。

目前,我有两种不同的方法来让我的代码做我想要它做的事情,但我都不喜欢,我正在寻找一个适用于执行此类操作的C++概念(我不可能是第一个需要这种行为的人)。

我目前所拥有的及其原因:

1. 将所有(相关的)继承类声明为基类的友元:

class A{
    ....
    friend class B;
    friend class C;
};

我不喜欢这个解决方案,因为每次我写一个新的子类时就需要修改我的基类,这正是我想要避免的。 (我只想在系统的主模块中使用“A”接口。)
2. 将 A* 指针转换为继承类的指针并使用它工作。
void B::func(){
    B *uglyHack = static_cast<B*>(myA);
    std::cout << uglyHack->var + 1 << std::endl;
}

变量名对我的情感使用这种方法提出了明显的建议,但这是我现在正在使用的方法。由于我设计了这些类,我知道如何小心谨慎地使用仅实际实现在 class A 中的东西,同时将其视为 class B 。但是,如果其他人继续开展我的项目工作,他可能不太熟悉代码。此外,将变量指针强制转换为我非常清楚它不是的东西,让我感到纯粹的邪恶。
我试图尽可能保持这个项目的代码整洁和设计良好,因此如果有人对不需要每隔一段时间修改基础类或使用邪恶概念的解决方案有任何建议,我会非常感激。

我相信在装饰器模式中,装饰器和实际对象实现一个接口,也就是说,装饰器并不扩展正在被实现的实际对象,只提供相同的接口,并且装饰仅应用于公共接口。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas 我想在您输入评论时刚好编辑了解释 :) 您所说的是正确的,但在我的实际情况中,最顶层的接口包含一些对于该类的每个具体实例都通用的数据。因此,理论上,我不想访问任何属于任何具体实现的对象,只是一些共同实现在接口中的每个类实例的对象。但是,正如我在问题中刚刚编辑的那样,示例代码并不专注于实现一个Decorator示例,而只是解释了我的问题情况的示例。 - penelope
我很难理解你试图做什么。我知道你想给一个简单的例子,但现在我不知道代码中哪些奇怪的东西是重要的,哪些不是。例如,在B的构造函数中,你复制了var成员,但在C类中,你直接使用了myA->var。为什么需要复制,难道你不能在计算中使用复制吗?让我们从这里开始,也许会更清楚一些。 - Lubo Antonov
@lucas1024 问题是,原始示例只展示了我想要访问的内容、位置和方式,但并不太像装饰器模式(它只包含了一个真正的装饰器实现中的相同对象)。实际上,在基类中我有比1或2个变量更多的变量,对于简单的变量(例如标志),我不介意复制,但对于重量级的变量(例如向量 - 刚刚添加)我想复制。当您留下评论时,我在示例中仅使用了1个变量,我确实添加了我真正想要访问的内容的解释。 - penelope
3个回答

2

我认为您可能需要重新考虑设计,但解决“如何访问成员?”的特定问题的方法可能是:

class A{
protected:
    int var;

    static int& varAccessor( A& a ) {
       return a.var;
    }
};

然后在派生类型中调用受保护的访问器,通过引用传递成员对象:

varAccessor( this->myA ) = 5;

如果你在考虑装饰器模式,我认为这不是正确的方法。 混淆的根源在于大多数人没有意识到一个类型有两个单独的接口,一个是面向用户的public接口,另一个是面向实现提供者(即派生类型)的virtual接口。在许多情况下,函数既是public又是virtual(即语言允许绑定这两个语义上不同的接口)。在装饰器模式中,您使用基本接口来提供实现。继承存在的原因是让派生类型通过一些实际工作(装饰)并将工作转发给实际对象来为用户提供操作。继承关系并不是为了通过受保护元素以任何方式访问实现对象,这本身就是危险的。如果您传递了一个具有更严格不变量的派生类型对象(即对于X类型的对象,var必须是奇数),那么您采取的方法会让某种形式的“装饰器”打破应该只是装饰的X类型的不变量。


你所说的实际上是在所有评论中最有意义的。我会再仔细考虑你在这里写的内容(关于A到这个问题和设计问题),也许会单独发布一个关于如何设计系统的问题。我仍然认为装饰器是正确的选择,但也许有人会有更好的建议。但在这里,这已经偏离了主题。 - penelope
我想我终于找到了一个不错的方法来实现我想要的功能...如果你不介意的话,我希望你能评论一下我的解决方案,因为迄今为止你的评论和答案已经让我想到了很多关于我的代码的好问题。 - penelope

0

我认为我找到了一种不错的方法来实现我想要的继承结构。

首先,在基类(作为所有其他类的基类,以及装饰器模式中的抽象基类接口)中,我仅为第一个子类(充当抽象装饰器接口的子类)添加了一个友元类声明:

class A{

    ....
    friend class B;
};

然后,在子类中为基类中所有有趣的变量添加protected访问函数:

class B : public A{

    ...
    protected:
        A *myA;
        int getAVar() {return myA->var;}
        std::vector <double> &getAHeavyVar {return myA->heavyVar;}
};

最后,我可以以一种受控的方式(与static_cast<>相反)通过访问函数从所有继承class B的类中仅访问我需要的内容(即那些将成为具体装饰器),而无需使B的所有子类成为class Afriend
class C : public B{

    ....
    public:
        virtual void func() {
        B::func(); // base functionality
        int heavyCalc = lotsOfCalc(this->getAHeavyVar); // legal now!
            // here, I actually access a heavy object (not int), and thus
            // would not like to copy it
        std::cout << "Expanded functionality " << heavyCalc << std::endl;
        std::cout << "And also the int: " << this->getAVar << std::endl;
            // this time, completely legal
    }
};

我也试图只给B类中的某些函数赋予友元访问权限(将它们声明为友元函数),但这并不起作用,因为我需要在A类中的友元声明之前在B类内部声明这些函数。由于在这种情况下B类继承了A类,这会导致循环依赖关系(B类前向声明对于仅使用友元函数是不够的,但对于友元类声明则可以正常工作)。


1
你让我评论:设计问题仍然存在。两个接口仍然混淆,你从不是接口的类型继承,只是为了借用接口,然后强制访问不属于你的成员。这些都是设计问题,而不是实现问题。你在这里实现了那个设计,并且有着相同的问题。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas非常感谢您的评论。我已经将设计问题发布为单独的问题,希望有人能够提出比我目前更好的解决方案。 - penelope

0

我找不到任何使用装饰器模式的类似例子。在C++中,它似乎被用来装饰并委托回decoratee的公共抽象接口,而不是从中访问非公共成员。

事实上,在您的示例中,我没有看到发生装饰。您只是改变了子类的行为,这表明您只想要普通的继承(请考虑如果您使用B来装饰另一个B,效果不会像正常装饰那样链接)。


没错,我在代码示例中没有放置任何实际的装饰,我只写了一个最小的示例来访问成员,就像我想访问它们一样,以便尽可能地关注我的问题。如果您认为这样很混乱,我现在会扩展我的示例代码。 - penelope

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