Tại sao chúng ta không thể khai báo std :: vector <AbstractClass>?


88

Đã dành khá nhiều thời gian để phát triển trong C #, tôi nhận thấy rằng nếu bạn khai báo một lớp trừu tượng với mục đích sử dụng nó làm giao diện, bạn không thể khởi tạo một vectơ của lớp trừu tượng này để lưu trữ các thể hiện của các lớp con.

#pragma once
#include <iostream>
#include <vector>

using namespace std;

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};

class FunnyContainer
{
private:
    std::vector <IFunnyInterface> funnyItems;
};

Dòng khai báo vectơ của lớp trừu tượng gây ra lỗi này trong MS VS2005:

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

Tôi thấy một cách giải quyết rõ ràng, đó là thay thế IFunnyInterface bằng như sau:

class IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        throw new std::exception("not implemented");
    }
};

Đây có phải là một cách giải quyết chấp nhận được C ++ khôn ngoan không? Nếu không, có thư viện nào của bên thứ ba như boost có thể giúp tôi giải quyết vấn đề này không?

Cảm ơn vì đã đọc nó !

Anthony

Câu trả lời:


127

Bạn không thể khởi tạo các lớp trừu tượng, do đó một vector của các lớp trừu tượng không thể hoạt động.

Tuy nhiên, bạn có thể sử dụng một vectơ con trỏ đến các lớp trừu tượng:

std::vector<IFunnyInterface*> ifVec;

Điều này cũng cho phép bạn thực sự sử dụng hành vi đa hình - ngay cả khi lớp không trừu tượng, việc lưu trữ theo giá trị sẽ dẫn đến vấn đề cắt đối tượng .


5
hoặc bạn có thể sử dụng std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>> nếu bạn không muốn xử lý thời gian tồn tại của đối tượng theo cách thủ công.
Sergey Teplyakov

4
Hoặc thậm chí tốt hơn, boost :: ptr_vector <>.
Roel

7
Hoặc bây giờ, std :: vector <std :: unique_ptr <IFunnyInterface>>.
Kaz Dragon

21

Bạn không thể tạo một vectơ của một loại lớp trừu tượng vì bạn không thể tạo các thể hiện của một lớp trừu tượng và các vùng chứa Thư viện Chuẩn C ++ như các giá trị lưu trữ vectơ std :: (tức là các thể hiện). Nếu bạn muốn làm điều này, bạn sẽ phải tạo một vectơ con trỏ tới kiểu lớp trừu tượng.

Cách giải quyết của bạn sẽ không hoạt động vì các hàm ảo (đó là lý do tại sao bạn muốn lớp trừu tượng ngay từ đầu) chỉ hoạt động khi được gọi thông qua con trỏ hoặc tham chiếu. Bạn cũng không thể tạo vectơ tham chiếu, vì vậy đây là lý do thứ hai tại sao bạn phải sử dụng vectơ con trỏ.

Bạn nên nhận ra rằng C ++ và C # có rất ít điểm chung. Nếu bạn đang có ý định học C ++, bạn nên bắt đầu từ đầu và đọc một hướng dẫn về C ++ chuyên dụng tốt như Accelerated C ++ của Koenig và Moo.


Cảm ơn bạn đã giới thiệu một cuốn sách ngoài việc trả lời bài đăng!
BlueTrin

Nhưng khi bạn khai báo một vectơ của các lớp trừu tượng, bạn không yêu cầu nó tạo ra bất kỳ lớp Trừu tượng nào, mà chỉ là một vectơ có khả năng chứa một lớp con không trừu tượng của lớp đó? Trừ khi bạn chuyển một số vào phương thức khởi tạo vectors thì làm sao nó có thể biết được có bao nhiêu trường hợp của lớp trừu tượng cần tạo?
Jonathan.

6

Trong trường hợp này, chúng tôi không thể sử dụng ngay cả mã này:

std::vector <IFunnyInterface*> funnyItems;

hoặc là

std::vector <std::tr1::shared_ptr<IFunnyInterface> > funnyItems;

Bởi vì không có mối quan hệ LÀ mối quan hệ giữa FunnyImpl và IFunnyInterface và không có sự chuyển đổi ngầm nào giữa FUnnyImpl và IFunnyInterface vì thừa kế riêng.

Bạn nên cập nhật mã của mình như sau:

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};

1
Hầu hết mọi người nhìn qua thừa kế riêng tôi nghĩ :) Nhưng chúng ta không nhầm lẫn giữa OP thậm chí nhiều hơn :)
Roel

1
Vâng. Đặc biệt là sau câu nói của người bắt đầu chủ đề: "Đã dành khá nhiều thời gian để phát triển trong C #" (nơi không có thừa kế riêng).
Sergey Teplyakov

6

Phương pháp thay thế truyền thống là sử dụng một vectorcon trỏ, như đã được lưu ý.

Đối với những người đánh giá cao, Boostđi kèm với một thư viện rất thú vị: Pointer Containershoàn toàn phù hợp cho nhiệm vụ và giải phóng bạn khỏi các vấn đề khác nhau được ngụ ý bởi các con trỏ:

  • quản lý suốt đời
  • tham chiếu kép các trình vòng lặp

Lưu ý rằng điều này tốt hơn đáng kể so với vectorcon trỏ thông minh, cả về hiệu suất và giao diện.

Bây giờ, có một giải pháp thay thế thứ 3, đó là thay đổi hệ thống phân cấp của bạn. Để cách nhiệt tốt hơn cho người dùng, tôi đã thấy một số lần mẫu sau được sử dụng:

class IClass;

class MyClass
{
public:
  typedef enum { Var1, Var2 } Type;

  explicit MyClass(Type type);

  int foo();
  int bar();

private:
  IClass* m_impl;
};

struct IClass
{
  virtual ~IClass();

  virtual int foo();
  virtual int bar();
};

class MyClass1: public IClass { .. };
class MyClass2: public IClass { .. };

Điều này khá đơn giản và là một biến thể của Pimplthành ngữ được làm giàu bởi một Strategymẫu.

Tất nhiên, nó chỉ hoạt động trong trường hợp bạn không muốn thao tác trực tiếp các đối tượng "true" và liên quan đến bản sao sâu. Vì vậy, nó có thể không phải là những gì bạn mong muốn.


1
Cảm ơn bạn đã tham khảo Boost và mẫu thiết kế
BlueTrin

2

Bởi vì để thay đổi kích thước một vectơ, bạn cần sử dụng hàm tạo mặc định và kích thước của lớp, do đó yêu cầu nó phải cụ thể.

Bạn có thể sử dụng một con trỏ như đề xuất khác.


1

std :: vector sẽ cố gắng cấp phát bộ nhớ để chứa kiểu của bạn. Nếu lớp của bạn hoàn toàn là ảo, vector không thể biết kích thước của lớp mà nó sẽ phải cấp phát.

Tôi nghĩ rằng với cách giải quyết của bạn, bạn sẽ có thể biên dịch vector<IFunnyInterface>nhưng bạn sẽ không thể thao tác FunnyImpl bên trong nó. Ví dụ: nếu IFunnyInterface (lớp trừu tượng) có kích thước 20 (tôi thực sự không biết) và FunnyImpl có kích thước 30 vì nó có nhiều thành viên và mã hơn, bạn sẽ cố gắng đưa 30 vào vectơ 20 của mình

Giải pháp sẽ là cấp phát bộ nhớ trên heap với "mới" và lưu trữ các con trỏ trong vector<IFunnyInterface*>


Tôi nghĩ đây là câu trả lời, nhưng nhìn lên trả lời gf và đối tượng cắt, nó giải thích chính xác những gì sẽ xảy ra trong container
BlueTrin

Câu trả lời này mô tả những gì sẽ xảy ra nhưng không sử dụng từ 'cắt', vì vậy câu trả lời này là chính xác. Khi sử dụng vectơ ptrs, sẽ không xảy ra hiện tượng cắt. Đó là toàn bộ điểm của việc sử dụng ptrs ngay từ đầu.
Roel

-2

Tôi nghĩ rằng nguyên nhân gốc rễ của hạn chế thực sự đáng buồn này là thực tế là các nhà xây dựng không thể ảo. Trình biên dịch của nó không thể tạo mã sao chép đối tượng mà không biết thời gian của nó trong thời gian biên dịch.


2
Đây không phải là nguyên nhân gốc rễ và nó không phải là một "hạn chế đáng buồn".

Vui lòng giải thích lý do tại sao bạn nghĩ rằng nó không phải là một hạn chế? Thật tuyệt nếu có khả năng. Và có một số chi phí cho lập trình viên khi anh / cô ấy buộc phải đặt con trỏ đến vùng chứa và lo lắng về việc xóa. Tôi đồng ý rằng có các đối tượng có kích thước khác nhau trong cùng một vùng chứa sẽ làm giảm hiệu suất.
David Gruzman,

Chức năng ảo điều khiển dựa trên loại đối tượng bạn có. Toàn bộ điểm của nhà thầu là không có một đối tượng chưa . Liên quan đến lý do tại sao bạn không thể có các hàm ảo tĩnh: cũng không có đối tượng.
MSalters

Tôi có thể nói rằng mẫu của vùng chứa lớp không cần đối tượng, nhưng một nhà máy sản xuất lớp và phương thức khởi tạo là một phần tự nhiên của nó.
David Gruzman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.