首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >理解c++多态性的困难

理解c++多态性的困难
EN

Stack Overflow用户
提问于 2020-10-27 11:46:33
回答 2查看 91关注 0票数 1

我想要实现的是创建一个子类对象的超类数组。

在我正在做的这个特定的测试中,我想要有一个动物数组,它有一些狗对象和一些猫对象,同时它们保持自己的属性。

代码语言:javascript
复制
#include <iostream>
using namespace std;

//ANIMAL
class animal
{
protected:
    int ID;
    string name;
public:
    animal(string = "Unknown");
    int get_ID() { return ID; }
    virtual string get_name() { return name; }
};

animal::animal(string n) { name = n; }

//DOG
class dog : public animal
{
    static int newID;
    string sound;
public:
    dog(string = "Corgi", string = "Woof!");
    string get_name() { return sound + " " + name; }
};

int dog::newID = 0;

dog::dog(string n, string s) : animal(n)
{
    newID++;
    ID = newID;
    cout << ID << "\t";
    sound = s;
}

//CAT
class cat : public animal
{
    static int meowID;
    string color;
public:
    cat(string = "Munchkin", string = "Calico");
    string get_name() { return color + " " + name; }
};

int cat::meowID = 89;

cat::cat(string n, string c) : animal(n)
{
    meowID++;
    ID = meowID;
    cout << ID << "\t";
    color = c;
}


//MAIN
int main(int argc, char* argv[])
{
    animal** test;
    animal* p;
    for (int i = 0; i < 6; i++)
    {
        p = new dog;
        p++;
    }
    cout << "\n";
    for (int i = 0; i < 6; i++)
    {
        p = new cat;
        p++;
    }
    cout << "\n";
    test = &p;
    cout << (*test-7)->get_ID();
    return 0;
}

到目前为止,我所学到的是p不是一个数组,它通过循环一直指向不同的内存地址。

我无法执行animal** test = new dog[6];,因为它是无效的初始化。即使这样可以工作,我也会在级联cat的另一个数组段时遇到麻烦。

这是我得到的输出:

代码语言:javascript
复制
1       2       3       4       5       6
90      91      92      93      94      95
0

第一行显示被调用6次的狗is,第二行显示被调用6次的猫is。(*test-7)->get_ID();是最后一个数字。

看起来构造函数是被正确调用的。然而,我不知道我的指针指向哪里,因为我期望的是91而不是0。

如何获得一个可以从每个元素访问信息的动物数组?例如,

代码语言:javascript
复制
animal** myArray;
{do something}
cout << myArray[2].get_name() << endl << myArray[7].get_ID();

并且它输出

代码语言:javascript
复制
Woof! Corgi
91
EN

回答 2

Stack Overflow用户

发布于 2020-10-27 12:19:06

关于animal类的一个重要细节是:当多态类型的析构函数被调用,但这些析构函数不是virtual时,多态类型可能会遇到问题。建议您将基类的析构函数设为虚的,即使该类本身实际上并不需要析构函数。在这种情况下,您可以告诉编译器您希望析构函数为virtual,但使用以下命令生成它的默认实现:

代码语言:javascript
复制
virtual ~animal() = default;

将以上行添加到animal类的public:部分。这将确保您稍后定义的任何派生类都将自动获得虚拟析构函数。

现在来看你剩下的代码:

代码语言:javascript
复制
p = new dog;

到现在为止还好。但接下来是这样:

代码语言:javascript
复制
p++;

除了使指针指向无效地址之外,不做任何有用的事情。然后,在下一次迭代中,将执行另一个p = new dog;。您之前分配的dog对象现在将永远丢失。你有所谓的“泄密”。

似乎你期望new以一种将对象一个接一个地放入内存的方式来分配对象。事实并非如此。new将在不可预测的位置分配内存。因此,这是:

代码语言:javascript
复制
*test-7

无法工作,因为对象在内存中的布局不是您所期望的方式。相反,您得到的是指向最近分配的对象之前的某个内存位置7“位置”的地址,该地址几乎肯定不指向您希望的animal对象。当你后来取消引用时,你得到了未定义的行为。一旦发生这种情况,你就不能再对结果进行推理了。它们可以是任何东西,从看到打印错误的文本到你的程序崩溃。

如果你需要一个animal指针数组,你应该专门创建一个:

代码语言:javascript
复制
animal* animals[12];

这将创建一个包含12个animal指针的名为animals的数组。然后,您可以初始化这些指针:

代码语言:javascript
复制
for (int i = 0; i < 6; i++) {
    animals[i] = new dog;
}

cout << "\n";

for (int i = 6; i < 12; i++) {
    animals[i] = new cat;
}

然后,您只需指定要访问的数组索引:

代码语言:javascript
复制
cout << animals[0]->get_ID() << '\n'; // first animal
cout << animals[6]->get_ID() << '\n'; // seventh animal

处理完数组后,不要忘记删除对象。由于animals是一个数组,因此可以使用ranged循环删除其中的所有对象:

代码语言:javascript
复制
for (auto* animal_obj : animals) {
    delete animal_obj;
}

但是,所有这些低级代码都相当单调且容易出错。建议改用为您执行分配和清理的库工具,例如本例中的std::unique_ptr。作为第一步,可以用std::unique_ptr<animal>替换原始的animal*指针

代码语言:javascript
复制
unique_ptr<animal> animals[12];

(不要忘记在源文件中使用#include <memory>,因为std::unique_ptr是由该库头提供的。)

现在你有了一个智能指针数组,而不是原始指针。您可以使用以下命令初始化该数组:

代码语言:javascript
复制
for (int i = 0; i < 6; i++) {
    animals[i] = make_unique<dog>();
}

cout << "\n";

for (int i = 6; i < 12; i++) {
    animals[i] = make_unique<cat>();
}

现在你不需要delete任何东西了。一旦智能指针超出作用域(在本例中,这意味着一旦animals数组超出作用域,当您的main()函数退出时),智能指针将自动为您完成此操作。

第二步,您可以用std::vectorstd::array替换animals数组。您选择哪一个取决于您是否希望您的阵列以后能够增长或缩小。如果数组中只需要12个对象,那么std::array可以做到:

代码语言:javascript
复制
array<unique_ptr<animal>, 12> animals;

(您需要#include <array>。)

其他一切都不会改变。for循环保持不变。

与普通数组(也称为“内置数组”)相比,std::array是更好的选择,因为它提供了一个.size()成员函数,该函数告诉您数组可以容纳的元素数量。因此,您不必手动跟踪数字12。此外,当您将std::array传递给以animal*为参数的函数时,它不会像普通数组那样衰减为指针。这可以防止一些常见的编码错误。如果您想实际从std::array获取animal*指针,可以使用它的.data()成员函数,该函数返回指向数组第一个元素的指针。

如果您希望数组能够在运行时增大或缩小,而不是具有在编译时设置的固定大小,则可以使用std::vector

代码语言:javascript
复制
vector<unique_ptr<animal>> animals;

(您需要#include <vector>。)

这将创建一个可以存储unique_ptr<animal>类型的元素的空向量。要向其中实际添加元素,可以使用std::vector.push_back()函数

代码语言:javascript
复制
// Add 6 dogs.
for (int i = 0; i < 6; i++) {
    animals.push_back(make_unique<dog>());
}
// Add 6 cats.
for (int i = 0; i < 6; i++) {
    animals.push_back(make_unique<cat>());
}

您可以使用emplace_back()作为优化,而不是push_back(),但在这种情况下,这无关紧要。这里要记住的关键点是,一旦你将元素推入向量中,它就会自动增长。它将自动完成此操作,而无需您手动分配新元素。这使得编写代码变得更容易,更不容易出错。

一旦向量超出作用域(这里是当main()返回时),向量将自动删除它为存储元素而分配的内存,并且由于这些元素是智能指针,它们反过来将自动删除它们所指向的animal对象。

票数 4
EN

Stack Overflow用户

发布于 2020-10-27 12:14:26

如果您是C++的新手,重要的是要从正确的角度出发,并遵循现代最佳实践,即:

  • 使用std::vector<T> (如果您有固定大小的集合,则使用std::array<T,N> ),而不是new[]p** (并且永远不要在C++中直接使用malloccalloc!)
  • 我注意到,通常您应该更喜欢组合而不是继承,
  • 我还注意到,当编译时知道可能的子类集合时,您应该考虑使用联合类型而不是子类化,因为它允许使用者在不需要使用RTTI或猜测的情况下详尽地处理返回值。这可以通过using AnAnimal = std::variant<cat,dog>.

来实现

不管怎样,这就是我想出来的。class animalclass dogclass cat代码与您发布的代码相同(位于// #region注释中),但是顶部的#includeusing语句不同,main方法也不同。

请注意,我的代码假设您有一个符合C++14语言规范和STL的编译器。您的编译器可能默认使用C++11或更早的版本。std::make_uniquestd::move函数需要C++14。

如下所示:

代码语言:javascript
复制
#include <iostream>
#include <memory>
#include <vector>
#include <string>

// Containers:
using std::vector;
using std::string;

// Smart pointers:
using std::unique_ptr;
using std::move;
using std::make_unique;

// IO:
using std::cout;
using std::endl;

// #region Original classes

//ANIMAL
class animal
{
protected:
    int ID;
    string name;
public:
    animal(string = "Unknown");
    int get_ID() { return ID; }
    virtual string get_name() { return name; }
};

animal::animal(string n) { name = n; }

//DOG
class dog : public animal
{
    static int newID;
    string sound;
public:
    dog(string = "Corgi", string = "Woof!");
    string get_name() { return sound + " " + name; }
};

int dog::newID = 0;

dog::dog(string n, string s) : animal(n)
{
    newID++;
    ID = newID;
    cout << ID << "\t";
    sound = s;
}

//CAT
class cat : public animal
{
    static int meowID;
    string color;
public:
    cat(string = "Munchkin", string = "Calico");
    string get_name() { return color + " " + name; }
};

int cat::meowID = 89;

cat::cat(string n, string c) : animal(n)
{
    meowID++;
    ID = meowID;
    cout << ID << "\t";
    color = c;
}

// #endregion

int main()
{
    // See https://stackoverflow.com/questions/44434706/unique-pointer-to-vector-and-polymorphism
    
    vector<unique_ptr<animal>> menagerie;
    
    // Add 6 dogs:
    for( int i = 0; i < 6; i++ ) {
        menagerie.emplace_back( make_unique<dog>() );
    }
    
    // Add 6 cats:
    for( int i = 0; i < 6; i++ ) {
        menagerie.emplace_back( make_unique<cat>() );
    }
    
    // Dump:
    for ( auto &animal : menagerie ) {
        cout << "Id: " << animal->get_ID() << ", Name: \"" << animal->get_name() << "\"" << endl;
    }

    return 0;
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64548052

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档