引用来自C ++中另一个(不相关的)类的基类的派生类的对象 [英] Referring to an object of a derived class from the base class of another (unrelated!) class in C++

查看:148
本文介绍了引用来自C ++中另一个(不相关的)类的基类的派生类的对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果你从一开始就不能理解问题标题,这不是你的错 - 我想不出更好的描述。



在我的程序的初始版本中,我有一个生态系统类和一个个人类:

  //非常简化,为了说明目的

class Ecosystem
{
protected:
// int只是个人的ID。
std :: map< int,std :: shared_ptr< Individual> >个人;

public:
Ecosystem();
void func(int _individual_id)
{
std :: cout< 个人年龄:
<个人[_individual_id] - > get_age()
< std :: endl;
}

void routine(int _individual_id)
{
//通过
工作的另一个函数//个体中的指针。
}

//更多这样的函数...
};

class个人
{
protected:
int age;

public:
Individual();
inline int get_age()const
{
return age;
}
};

Ecosystem类包含了许多函数,未来我会添加更多。 p>

我现在决定将Individual类拆分为一个基类和两个派生类,比如TypeAIndividual和TypeBIndividual,因为它们都有成员和属性,而另一个不需要(他们也通过基类共享几个成员和属性)。所以我有基类Individual类和两个派生类:

  class TypeAIndividual:public Individual 
{
protected:
//特定于A类个人的数据结构

public:
TypeAIndividual();
};

class TypeBIndividual:public Individual
{
protected:
//特定于B类个人的数据结构

public:
TypeBIndividual();
};问题是,生态系统现在还需要拆分为TypeAEcosystem和TypeBEcosystem:

  class Ecosystem 
{
protected:
//保存指向基础的指针个别类是无意义的不想要)
// std :: map< int,std :: shared_ptr< Individual> >个人;

public:
Ecosystem();
//我想在基类
//中保留func(),因为它只访问属性和
//从Individual派生的两个类共同的成员
//。
void func(int _individual_id)
{
// Hmmmm ...
//指针不再存在于生态系统类中了!
std :: cout<< 个人年龄:
<个人[_individual_id] - > get_age()
< std :: endl;
}
//可以在每个类中实现
//从Ecosystem派生。
virtual void routine(int _individual_id)= 0;
};

类TypeAEocosystem:public生态系统
{
protected:
//指向个人
//对应类型的指针。
std :: map< int,std :: shared_ptr< TypeAIndividual> >个人;

public:
TypeAEcosystem();
//重新实现routine()是OK
//因为它的事情特定于
//这个单独的类型。
virtual void routine(int _individual_id)
{
//在数据结构上操作特定的
//到这种类型的个体。
}

};

类TypeBEcosystem:public生态系统
{
protected:
//指向个人
//对应类型的指针。
std :: map
public:
TypeBEcosystem();
//重新实现routine()是OK
//因为它的事情特定于
//这个单独的类型。
virtual void routine(int _individual_id)
{
//在数据结构上操作特定的
//到这种类型的个体。
}
};

TypeAEcosystem和TypeBEcosystem都使用 void func(int _individual_id),它需要访问相应类型的个人。但是基类Ecosystem不包含指向个人的指针,因为 std :: map 在每个派生类中,而不在基类中。



我的问题是:如何访问适当类型的个人( TypeAIndividual TypeBIndividual ),同时避免在从Ecosystem衍生的每个类中实现单独的 void func(int _individual_id)换句话说,有没有办法保持 func()在基类中,以便当我更改它,我不必更改派生类?在实际的程序中,有几个函数 func()只需要一个 int 作为参数。另外,这些函数中的一些函数从 Ecosystem 类中的其他结构中获取单独的ID,因此我不能简单地传递一个指向 TypeAIndividual TypeBIndividual



我认为的事情




  • 合并 TypeAIndividual TypeBIndividual 回到一个通用的 Individual 两个派生类。


  • 制作 func()& Co.虚拟并在 TypeAEcosystem TypeBEcosystem 中实现。这意味着如果我想更改任何功能,我必须更改这两个实现(=维护噩梦)。


  • 生态系统类包含两种类型个体的 std :: map ,如下:

      //似乎笨重... 
    class生态系统
    {
    protected:
    //注:生态系统可以包含
    //一个或另一个,但不能同时包含两个!
    //一个地图总是空的。
    std :: map< int,std :: shared_ptr< TypeAIndividual> > type_a_individuals;

    std :: map< int,std :: shared_ptr< TypeBIndividual> > type_b_individuals;

    public:
    生态系统();
    void func(int _individual_id)
    {
    //检查我们
    //正在使用的个人类型,并在
    //适当的容器上操作。
    if(type_a_individuals.size()> 0)
    {
    std :: cout< 个人年龄:
    < type_a_individuals [_individual_id] - > get_age()
    << std :: endl;
    }
    else
    {
    std :: cout< 个人年龄:
    < type_b_individuals [_individual_id] - > get_age()
    << std :: endl;
    }
    }
    };




这需要在每个函数中插入一个检查,这在可维护性方面几乎和在不同类中的函数一样糟糕。



注意:虽然我非常想避免传递指针,我会考虑向上转换和/或向下转换(如果它解决了问题)。



欢迎任何建议! / p>



编辑1



梦幻般的回应!正如amit和Chris所建议的,并且看着我的生态系统类,当然,它太庞大了。我把成员函数移动到其他类,现在我下来在生态系统类中的四个或五个基本功能。 生态系统类驻留在库中,并提供了一个用于与个人进行实验的接口,但我不希望用户能够操作 Individual s和其他类直接,所以我不能完全取消它。



我喜欢所有的建议,有一些巧妙的解决方案。话虽如此,克里斯提出的建议立即抓住了我的注意,因为它非常整洁,允许我有一个单一的生态系统类,而不是三个单独的类(基础和两个派生)。个人的类型可以在配置文件中指定,并且我可以在同一个实验中从不同的配置文件生成多个生态系统。这是被接受的答案。



再次感谢大家的建设性输入!

解决方案

正如我在评论中所说的,你可以考虑将生态系统作为一个模板化类,并为每个IndivualType生成一个生态系统实例。

 code> template< class IndivualType> 
class Ecosystem {
protected:
// int只是个人的ID。
std :: map< int,std :: shared_ptr< IndivualType> >个人;
public:
// ...
};

如果您需要生态系统对给定IndividualType的行为不同,生态系统如此:

 模板<> 
class Ecosystem< SpecialIndividualType> {
protected:
// int只是个人的ID。
std :: map< int,std :: shared_ptr< SpecialIndividualType> >个人;
public:
// EcoSystem for SpecialIndividualType的特殊实现
};

这可能不是必须的,但是这可能是好事。



最后,正如你所说的

生态系统类包含了许多函数,我将来会添加更多。



您可能想考虑将您的生态系统的功能分为政策。我不知道你的需要,但只是作为一个例子:

  template< class IndivualType,class SomePolicy1,class SomePolicy2> 
class Ecosystem {
private:
const SomePolicy1 mSp1;
const SomePolicy2 mSp2;
protected:
// int只是个人的ID。
std :: map< int,std :: shared_ptr< IndivualType> >个人;
public:
Ecosystem(const SomePolicy1& sp1 = SomePolicy1(),const SomePolicy2& sp2 = SomePolicy2())):mSp1(sp1),mSp2(sp2){}
//。 ..
void func(int _individual_id)
mSp1.doSmth(_individual_id);
}

void func2(int _individual_id){
mSp2.doSmth(_individual_id);
}
};

这称为基于策略的设计,您可以在网上找到很多关于它的信息。



当然还有其他解决方案,例如使方法虚拟,如前所述。我可能会尝试两个(取决于你有的时间),看看你最舒服的感觉。


If you can't understand the question title from the onset, it's not your fault - I couldn't think of a better description. Here is the explanation of the problem, which might be a bit lengthy, so apologies in advance.

In the initial version of my program, I had an Ecosystem class and an Individual class:

// Very simplified, for illustration purposes

class Ecosystem
{
    protected:
        // The int is just the ID of the individual.
        std::map<int, std::shared_ptr<Individual> > individuals;

    public:
        Ecosystem();
        void func(int _individual_id)
        {
            std::cout << "Individual's age: " 
                      << individuals[_individual_id]->get_age() 
                      << std::endl;
        }

        void routine(int _individual_id)
        {
            // Another function working via
            // the pointers in individuals.
        }        

        // More such functions...
};

class Individual
{
    protected:
        int age;

    public:
        Individual();
        inline int get_age() const
        {
            return age;
        }
};

The Ecosystem class contains dozens of functions, and I will add a lot more in the future.

I have now decided to split the Individual class into a base class and two derived classes, say TypeAIndividual and TypeBIndividual, because they each have members and attributes that the other one does not need (they also share a few members and attributes via the base class). So I have the base Individual class and two derived classes:

class TypeAIndividual : public Individual
{
    protected:
        // Data structures specific to individuals of type A

    public:
        TypeAIndividual();
};

class TypeBIndividual : public Individual
{
    protected:
        // Data structures specific to individuals of type B

    public:
        TypeBIndividual();
};

The problem is that the ecosystem now also needs to be split into TypeAEcosystem and TypeBEcosystem:

class Ecosystem
{
    protected:
        // Holding pointers to the base Individual class is pointless (pun not intended)
        // std::map<int, std::shared_ptr<Individual> > individuals;

    public:
        Ecosystem();
        // I want to keep func() in the base class
        // because it only accesses attributes and
        // members common to both classes derived
        // from Individual. 
        void func(int _individual_id)
        {
            // Hmmmm...
            // The pointers don't live in the Ecosystem class any more!
            std::cout << "Individual's age: " 
                      << individuals[_individual_id]->get_age() 
                      << std::endl; 
        }
        // OK to implement in each class
        // derived from Ecosystem.
        virtual void routine(int _individual_id) = 0;
};

class TypeAEcosystem : public Ecosystem
{    
    protected:
        // Pointers to individuals
        // of the corresponding type.
        std::map<int, std::shared_ptr<TypeAIndividual> > individuals;

    public:
        TypeAEcosystem();
        // Reimplementing routine() is OK
        // because it does things specific to
        // this individual type.
        virtual void routine (int _individual_id)
        {
            // Operate on data structures particular
            // to this type of individual.
        }

};

class TypeBEcosystem : public Ecosystem
{
    protected:
        // Pointers to individuals
        // of the corresponding type.
        std::map<int, std::shared_ptr<TypeBIndividual> > individuals;

    public:
        TypeBEcosystem();
        // Reimplementing routine() is OK
        // because it does things specific to
        // this individual type.
        virtual void routine (int _individual_id)
        {
            // Operate on data structures particular
            // to this type of individual.
        }
};

TypeAEcosystem and TypeBEcosystem both use void func(int _individual_id), which needs to access individuals of the corresponding type. But the base class Ecosystem doesn't contain pointers to individuals any more because the std::maps are in each derived class and not in the base class.

My question is: how can I access the appropriate type of individual (TypeAIndividual or TypeBIndividual) while avoiding implementing separate void func(int _individual_id) in each class derived from Ecosystem? In other words, is there a way to keep func() in the base class so that when I change it, I don't have to make changes to the derived classes? In the actual program, there are dozens of functions like func() which take just an int as a parameter. Also, some of those functions take individual IDs from other structures in the Ecosystem class, so I can't simply pass a pointer to TypeAIndividual or TypeBIndividual.

Things I have considered

  • Merging TypeAIndividual and TypeBIndividual back into a common Individual class with all the data structures necessary for both derived classes. This strikes me as a particularly clumsy way of doing things, but at least it will work.

  • Making func() & Co. virtual and implementing them in TypeAEcosystem and TypeBEcosystem. This means that if I want to make a change in any of the functions, I have to change both implementations (= a maintenance nightmare).

  • Having only one Ecosystem class which holds std::maps of the two types of individuals, like this:

    // Seems clunky...
    class Ecosystem
    {
        protected:
            // Note: The Ecosystem can contain 
            // one OR the other, but not both!
            // One map will always be empty.
        std::map<int, std::shared_ptr<TypeAIndividual> > type_a_individuals;
    
        std::map<int, std::shared_ptr<TypeBIndividual> > type_b_individuals;
    
        public:
            Ecosystem();
            void func(int _individual_id)
            {
                // Check what type of individuals we 
                // are working with and operate on the
                // appropriate container.
                if (type_a_individuals.size() > 0)
                {
                    std::cout << "Individual's age: " 
                              << type_a_individuals[_individual_id]->get_age() 
                              << std::endl; 
                }
                else
                {
                    std::cout << "Individual's age: " 
                              << type_b_individuals[_individual_id]->get_age() 
                              << std::endl; 
                }
            }
    };
    

This would require inserting a check in every function, which is almost as bad in terms of maintainability as having the functions in separate classes.

Note: Although I would very much like to avoid passing pointers around, I would consider upcasting and/or downcasting as appropriate (as a last resort...) if it solves the problem.

Any suggestions are welcome!


Edit 1

Thank you all for the fantastic responses! As suggested by both amit and Chris, and looked at my Ecosystem class and sure enough, it was too bulky. I moved member functions around into other classes and now I'm down to four or five essential functions in the Ecosystem class. The Ecosystem class resides in a library and provides an interface for conducting experiments with individuals, but I don't want users to be able to manipulate Individuals and other classes directly, so I can't do away with it completely.

I liked all suggestions, there are some ingenious solutions. That being said, the one proposed by Chris grabbed my attention immediately for being very neat and allowing me to have a single Ecosystem class rather than three separate classes (base and two derived). The type of individual can be specified in a config file, and I can spawn multiple ecosystems from different config files within the same experiment. This is the accepted answer.

Thank you again everyone for the constructive input!

解决方案

As I already said in my comment you could consider making Ecosystem a templated class and have one instance of an Ecosystem for each IndivualType.

template <class IndivualType>
class Ecosystem {
  protected:
    // The int is just the ID of the individual.
    std::map<int, std::shared_ptr<IndivualType> > individuals;
  public:
    // ...
};

In case you need the Ecosystem to behave different for a given IndividualType, you can in addition explicitly specialize your Ecosystem like so:

template <>
class Ecosystem<SpecialIndividualType> {
protected:
        // The int is just the ID of the individual.
        std::map<int, std::shared_ptr<SpecialIndividualType> > individuals;
      public:
        // special implementation for EcoSystem for SpecialIndividualType 
};

This probably will not be necessary, however it may be good to know.

Finally as you said the

The Ecosystem class contains dozens of functions, and I will add a lot more in the future.

You may want to consider to split the functionality of your ecosystem into policies. I dont know your needs but just as an example:

template <class IndivualType, class SomePolicy1, class SomePolicy2>
class Ecosystem {
  private:
    const SomePolicy1 mSp1;
    const SomePolicy2 mSp2;
  protected:
    // The int is just the ID of the individual.
    std::map<int, std::shared_ptr<IndivualType> > individuals;
  public:
    Ecosystem (const SomePolicy1& sp1= SomePolicy1(), const SomePolicy2& sp2= SomePolicy2())) : mSp1(sp1), mSp2(sp2) {}
    // ...
    void func(int _individual_id) 
        mSp1.doSmth(_individual_id);
    }

    void func2(int _individual_id) {
        mSp2.doSmth(_individual_id);
    }
};

This is called "policy based design", you can find a lot of information about it on the web.

Of course there are other solutions as well, such as making the methods virtual as already mentioned. I would probably try both (depending on the time you have) and see what you feel most comfortable with.

这篇关于引用来自C ++中另一个(不相关的)类的基类的派生类的对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆