我知道这可能看起来像一个微不足道的问题,但我还没有找到一个优雅的C ++解决方案来解决以下问题。
我想表示一个对象“世界”的复杂(树状)层次结构。我们说Animals
。每只动物都有一些基本的const属性。
例如name
。然后它也有一些方法,但它们对于这个问题并不重要。
class Animal {
public:
const char *GetName() const;
protected:
const char *name;
};
class Insect : public Animal {
...
};
class Butterfly : public Insect {
...
};
在这个层次结构中,我想初始化每个派生(大)孩子中的name
。 这是一个优雅的解决方案?
同样重要的是,在这个“世界”中,只有树叶的实例。也就是说,没有对象“Animal”或“Insect”。但是会有“蝴蝶”,“蜜蜂”或“蚊子”这些物品。
我知道执行此操作的“标准”方法是将name
放入构造函数中:
Animal::Animal(const char *name) : name(name) {}
Insect::Insect(const char *name) : Animal(name) {}
Butterfly::Butterfly() : Insect("Butterfly") {}
但是如果有更多这些属性,派生类也需要一些初始化,层次结构有更多级别,它可能变得非常混乱:
Animal::Animal(const char *name) : name(name) {}
Vertebrate::Vertebrate(const char *name) : Animal(name) {}
Mammals::Mammals(const char *name) : Vertebrate(name) {}
Ungulate::Ungulate(const char *name) : Mammals(name) {}
Horse::Horse() : Ungulate("Horse") {}
我能看到的另一个选择是删除const
并直接在孙子的构造函数中分配:
class Animal {
public:
const char *GetName() const;
protected:
std::string name;
};
Horse::Horse() {this->name = "Horse";}
但这也不是最优的,因为const丢失了,而且更容易出错(初始化可能会被遗忘)。
有没有更好的方法呢?
答案 0 :(得分:2)
嗯 - 希望我没有因为那个答案而被锁定,但你可以使用实现name
- 属性的虚拟基类。因此,您不必在基础类中通过层次结构传播初始化,而是可以直接解决"非常基础"具有name
- 属性的构造函数。此外,你实际上会被强制在任何"孙子" -class中调用它,所以你不能偶然忘记它:
class NamedItem {
public:
NamedItem(const char* _name) : name(_name) {}
const char *GetName() const;
protected:
const char *name;
};
class Animal : public virtual NamedItem {
public:
Animal(int mySpecificOne) : NamedItem("") {}
};
class Insect : public Animal {
public:
Insect(int mySpecificOne) : Animal(mySpecificOne), NamedItem("") {}
};
class Butterfly : public Insect {
};
答案 1 :(得分:2)
优雅解决方案是通过初始化传递参数。例如,如果“name”变量是Butterfly的名称(例如“sally”或“david”),那么很明显它必须通过初始化来完成。如果你发现它是丑陋的,就像它在这里一样,它可能表明你的数据分解/类层次结构是错误的。在您的示例中,每个Butterfly对象都具有一组相同的属性,这些属性实际上是指它们的类而不是每个实例,即它们是类变量而不是实例变量。这意味着“Butterfly”类应该有一个指向常见“Insect_Impl”对象的静态指针(可能有一个指向单个“Animal_Impl”对象的指针等)或一组被覆盖的虚函数。 (下面我只展示了一个级别的层次结构,但你应该能够计算出更多级别)
// Make virtual inherited functionality pure virtual
class Animal {
private:
std::string objName; // Per object instance data
public:
virtual ~Animal(std::string n): objName(n) {}
virtual std::string const& getName() = 0; // Per sub-class data access
virtual std::string const& getOrder() = 0; // Per sub-class data access
std::string const& getObjName() { return this->objName; }
};
// Put common data into a non-inherited class
class Animal_Impl{
private:
std::string name;
public:
Animal_Impl(std::string n): name(n);
std::string const& getName() const { return this->name; }
};
// Inherit for per-instance functionality, containment for per-class data.
class Butterfly: public Animal{
private:
static std::unique< Animal_Impl > insect; // sub-class data
public:
Butterfly(std::string n): Animal(n) {}
virtual ~Butterfly() {}
virtual std::string const& getName() override {
return this->insect->getName(); }
virtual std::string const& getOrder() override {
static std::string order( "Lepidoptera" );
return order; }
};
// Class specific data is now initialised once in an implementation file.
std::unique< Animal_Impl > Butterfly::insect( new Animal_Impl("Butterfly") );
现在使用Butterfly类只需要每个实例数据。
Butterfly b( "sally" );
std::cout << b.getName() << " (Order " << b.getOrder()
<< ") is called " << b.getObjName() << "\n";
答案 2 :(得分:0)
您的备选方案或任何替代方案name
非const和受保护的问题是,无法保证子类将正确设置此属性。
以下课程给你什么?
class Animal {
public:
Animal(const char* something)
const char *GetName() const;
private:
const char *name;
};
保证Animal接口的不变性,这在进行多线程处理时可能是一个很大的优势。如果一个对象是不可变的,那么多个线程就可以使用它而不是一个关键资源。
我知道&#34;标准&#34;这样做的方法是将name放入构造函数中: ...但是如果有更多这些属性,派生类 还需要一些初始化,层次结构有更多的层次 变得一团糟
一点也不凌乱。假定对象A的成员只有一个地方被初始化,并且它位于其子类的构造函数中。