C++ vs. C# - 使用接口/纯虚类

3

我正在尝试在程序中使用纯虚类作为参数,但是编译器报错:

Error 1 error C2259: 'Person': cannot instantiate abstract class

我猜测我得到这个错误是因为 A) 不可能实例化一个抽象类 B) 我不能像在C#中使用接口那样使用抽象类

下面的C#程序说明了我在C++程序中要做的事情。如何在C++中使用抽象类编写通用代码?如果我被迫使用更专业的Person版本,例如Employee,则该代码不是真正的通用代码。我需要使用模板吗?

C ++程序

#include<iostream>
#include<vector>

class Person {
    public:
        virtual std::string getName() = 0;
        virtual void setName(std::string name) = 0;
        virtual std::string toString() = 0;
    private:
        std::string name;
};

class Employee : public Person {
    public:
        std::string getName() {
            return this->name;
        }

        void setName(std::string name) {
            this->name = name;
        }

    std::string toString() {
        return "name:" + this->name;
    }

    private:
        std::string name;
};

class Repository {
    public:
        void add(Person& p) {
            this->repo.push_back(p);
        }
    private:
        std::vector<Person> repo;
};

int main(int argc, char* argv[])
{
    Repository repo;

    Employee emp1;
    emp1.setName("John Doe");

    repo.add(emp1);

    return 0;
}

C# 程序

interface IPerson
{
    string GetName();
    void SetName(string name);
    string ToString();
}

class Employee : IPerson
{
    private string _name;

    public string GetName() {
        return this._name;
    }

    public void SetName(string name) {
        this._name = name;
    }

    public override string ToString() {
        return "name: " + this._name;
    }
}

class Repository
{
    private List<IPerson> _repo;

    public Repository() {
        this._repo = new List<IPerson>();
    }

    public void Add(IPerson p) {
        this._repo.Add(p);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Repository repo = new Repository();

        Employee emp1 = new Employee();
        emp1.SetName("John Doe");

        repo.Add(emp1);
    }
}

在C#中,在接口中放置string ToString();至少有点“奇怪”,因为所有的object派生对象(几乎所有对象)都有一个可重载的string ToString(); - xanatos
3个回答

3
问题在于Repository正在存储Person 对象,而这个类不能被实例化*。这是因为std::vector<Person>保存Person
您可以存储指向Person的指针,但必须确保它们的生存期与Repository实例至少一样长。例如,
/// class repository does not own Persons it holds
class Repository {
    public:
        void add(Person& p) {
            this->repo.push_back(&p);
        }
    private:
        std::vector<Person*> repo;
};

* 请注意,一般而言可以从派生类型的对象构造出基类对象。基础对象将从派生类型中的基础子对象构造而来(参见什么是对象切片?)。在您的情况下,由于基类类型是抽象的,因此此方法失败。


你应该解释 repo.push_back 想要创建一个被修剪过的“拷贝”Employee对象切片问题) - xanatos
@xanatos 的 repo 存储了 Person 对象。它不能存储其他任何东西。我认为这已经足够清楚了。 - juanchopanza
对于你来说很清楚,对我来说也很清楚,不再编写C++代码了,但对于那些从C#或Java转来的人来说并不清楚。对象切片对这些人来说并不直观,C++中“幕后”频繁使用的复制构造函数也是如此。 - xanatos
@xanatos 我认为对象切片是一个干扰。你不能存储无法实例化的东西。我会添加一个脚注。 - juanchopanza
即使Person是可实例化的(因为它是一个基类,而不是纯抽象类),代码也不能按照提问者所想的那样工作。您通过使用指针正确地避开了整个问题(并打开一个大问题,非专业读者在程序崩溃之前都不会注意到: 但您必须确保它们至少与仓库实例一样长寿,但我们将忽略这一点)... - xanatos

1
你的问题在于C++的内存处理与C#完全不同。完全不同。 std::vector<> 存储你添加到它里面的副本副本是重要的词。另一个问题是拷贝构造函数不能是虚函数(例如参见 Can we make a class copy constructor virtual in C++)。
现在... 当你执行 this->repo.push_back(p) 时,std::vector<>Person 中搜索拷贝构造函数(因为你使用了 std::vector<Person>),但它找不到,所以会出错。
请注意,一般来说,即使Person不是一个抽象类,你的代码也可能仍然是错误的,因为std::vector<>不会将一个Employee复制到另一个Employee,它会将一个Employee复制到一个切片下的Person(这通常被称为对象切片)(记住复制构造函数不是虚拟的?)...
解决方案,请从@juanchopanza给出的方案开始,但请记住句子您可以存储对Person的指针,但必须确保它们的生存期至少与Repository实例相同... 这非常重要!
类似于你的问题:c++: can vector<Base> contain objects of type Derived? 那里的被接受的答案建议使用std::vector<std::unique_ptr>

1
C#有值类型(结构体)和引用类型(类)。 C ++只有值类型+至少三种不同的解决方案,但都存在问题。 Boost提供了一个shared_ptr<T>的简陋垃圾收集器。使用它可以获得类似于C#的多态性和多个名称可以引用相同实例的语义。当然,这也是有缺陷的,因为它依赖于引用计数和RAII,所以它不能处理循环依赖关系;为此,您需要使用weak_ptr<T>来打破循环。我想一些这些智能指针已经被纳入了最近的C++标准。您最终会发现,与C ++一起工作更多地涉及正确使用语言,而不是制作解决问题的好软件。

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