Bạn sẽ không được thừa hưởng từ std :: vector


189

Ok, điều này thực sự rất khó để thú nhận, nhưng tôi có một sự cám dỗ mạnh mẽ vào lúc này để thừa hưởng từ std::vector.

Tôi cần khoảng 10 thuật toán tùy chỉnh cho vectơ và tôi muốn chúng là thành viên trực tiếp của vectơ. Nhưng tự nhiên tôi cũng muốn có phần còn lại của std::vectorgiao diện. Chà, ý tưởng đầu tiên của tôi, với tư cách là một công dân tuân thủ luật pháp, là có một std::vectorthành viên trong MyVectorlớp. Nhưng sau đó tôi sẽ phải tự phê duyệt lại tất cả giao diện của std :: vector. Quá nhiều để gõ. Tiếp theo, tôi nghĩ về thừa kế riêng tư, vì vậy thay vì phê duyệt các phương pháp, tôi sẽ viết một loạt các using std::vector::memberphần trong phần công khai. Điều này là tẻ nhạt quá thực sự.

Và tôi ở đây, tôi thực sự nghĩ rằng tôi chỉ có thể kế thừa công khai từ đó std::vector, nhưng đưa ra một cảnh báo trong tài liệu rằng lớp này không nên được sử dụng đa hình. Tôi nghĩ rằng hầu hết các nhà phát triển đủ khả năng để hiểu rằng điều này không nên được sử dụng đa hình.

Là quyết định của tôi hoàn toàn bất công? Nếu vậy, tại sao? Bạn có thể cung cấp một giải pháp thay thế có các thành viên bổ sung thực sự là thành viên nhưng sẽ không liên quan đến việc gõ lại tất cả giao diện của vectơ không? Tôi nghi ngờ điều đó, nhưng nếu bạn có thể, tôi sẽ hạnh phúc.

Ngoài ra, ngoài việc một số kẻ ngốc có thể viết một cái gì đó như

std::vector<int>* p  = new MyVector

Có bất kỳ nguy hiểm thực tế nào khác khi sử dụng MyVector không? Bằng cách nói thực tế, tôi loại bỏ những thứ như tưởng tượng một hàm đưa con trỏ đến vectơ ...

Vâng, tôi đã nêu trường hợp của tôi. Tôi từng phạm tội. Bây giờ tùy bạn tha thứ cho tôi hay không :)


9
Vì vậy, về cơ bản, bạn hỏi liệu có ổn khi vi phạm quy tắc chung dựa trên thực tế là bạn quá lười biếng để thực hiện lại giao diện của bộ chứa không? Vậy thì không, không phải vậy. Hãy xem, bạn có thể có những điều tốt nhất của cả hai thế giới nếu bạn nuốt viên thuốc đắng đó và thực hiện đúng cách. Đừng là anh chàng đó. Viết mã mạnh mẽ.
Jim Brissom

7
Tại sao bạn không / không muốn thêm chức năng bạn cần với các chức năng không phải là thành viên? Đối với tôi, đó sẽ là điều an toàn nhất để làm trong kịch bản này.
Simone

11
@Jim: std::vectorGiao diện của nó khá lớn và khi C ++ 1x xuất hiện, nó sẽ mở rộng rất nhiều. Đó là rất nhiều để gõ và nhiều hơn nữa để mở rộng trong một vài năm. Tôi nghĩ rằng đây là một lý do tốt để xem xét thừa kế thay vì ngăn chặn - nếu ai đó tuân theo tiền đề rằng các chức năng đó phải là thành viên (mà tôi nghi ngờ). Nguyên tắc để không xuất phát từ các thùng chứa STL là chúng không đa hình. Nếu bạn không sử dụng chúng theo cách đó, nó sẽ không áp dụng.
sbi

9
Phần cốt lõi của câu hỏi nằm trong một câu: "Tôi muốn họ là thành viên trực tiếp của vectơ". Không có gì khác trong câu hỏi thực sự quan trọng. Tại sao bạn "muốn" điều này? Vấn đề với việc chỉ cung cấp chức năng này là không phải thành viên là gì?
jalf

8
@JoshC: "Ngươi sẽ luôn luôn phổ biến hơn" ngươi sẽ ", và đó cũng là phiên bản được tìm thấy trong Kinh thánh King James (nói chung là những gì mọi người đang ám chỉ khi họ viết" ngươi sẽ không [...] "). Điều gì trên trái đất sẽ khiến bạn gọi nó là "lỗi chính tả"?
ruakh

Câu trả lời:


155

Trên thực tế, không có gì sai với thừa kế công khai std::vector. Nếu bạn cần điều này, chỉ cần làm điều đó.

Tôi sẽ đề nghị làm điều đó chỉ khi nó thực sự cần thiết. Chỉ khi bạn không thể làm những gì bạn muốn với các chức năng miễn phí (ví dụ: nên giữ một số trạng thái).

Vấn đề là MyVectormột thực thể mới. Nó có nghĩa là một nhà phát triển C ++ mới nên biết nó là cái quái gì trước khi sử dụng nó. Sự khác biệt giữa std::vectorvà là MyVectorgì? Cái nào tốt hơn để sử dụng ở đây và ở đó? Nếu tôi cần chuyển std::vectorđến MyVectorthì sao? Tôi có thể sử dụng swap()hay không?

Không sản xuất các thực thể mới chỉ để làm cho một cái gì đó để nhìn tốt hơn. Những thực thể này (đặc biệt, phổ biến như vậy) sẽ không sống trong chân không. Họ sẽ sống trong môi trường hỗn hợp với entropy liên tục tăng.


7
Phản biện duy nhất của tôi về điều này là người ta phải thực sự biết mình đang làm gì để làm điều này. Ví dụ: không giới thiệu các thành viên dữ liệu bổ sung vào MyVectorvà sau đó cố gắng chuyển nó vào các hàm chấp nhận std::vector&hoặc std::vector*. Nếu có bất kỳ loại chuyển nhượng sao chép nào liên quan đến việc sử dụng std :: vector * hoặc std :: vector &, chúng tôi có các vấn đề cắt mà các thành viên dữ liệu mới MyVectorsẽ không được sao chép. Điều tương tự cũng đúng với việc gọi trao đổi thông qua một con trỏ / tham chiếu cơ sở. Tôi có xu hướng nghĩ rằng bất kỳ loại phân cấp thừa kế nào có nguy cơ cắt đối tượng là một thứ xấu.
stinky472

13
std::vectorKẻ hủy diệt không phải virtual, do đó bạn không bao giờ nên thừa hưởng từ nó
André Fratelli

2
Tôi đã tạo một lớp được kế thừa công khai std :: vector vì lý do này: Tôi có mã cũ với lớp vectơ không STL và tôi muốn chuyển sang STL. Tôi đã thực hiện lại lớp cũ như là một lớp dẫn xuất của std :: vector, cho phép tôi tiếp tục sử dụng các tên hàm cũ (ví dụ: Count () thay vì size ()) trong mã cũ, trong khi viết mã mới bằng std :: vector chức năng. Tôi đã không thêm bất kỳ thành viên dữ liệu nào, do đó, hàm hủy của std :: vector hoạt động tốt cho các đối tượng được tạo trên heap.
Graham Asher

3
@GrahamAsher Nếu bạn xóa một đối tượng thông qua một con trỏ đến cơ sở và hàm hủy không phải là ảo, chương trình của bạn sẽ thể hiện hành vi không xác định. Một kết quả có thể có của hành vi không xác định là "nó hoạt động tốt trong các thử nghiệm của tôi". Một cái khác là nó gửi email cho bà của bạn lịch sử duyệt web của bạn. Cả hai đều tuân thủ tiêu chuẩn C ++. Nó thay đổi từ cái này sang cái khác với các bản phát hành điểm của trình biên dịch, HĐH hoặc pha của mặt trăng cũng được tuân thủ.
Yakk - Adam Nevraumont

2
@GrahamAsher Không, bất cứ khi nào bạn xóa bất kỳ đối tượng nào thông qua một con trỏ đến cơ sở mà không có hàm hủy ảo, đó là hành vi không xác định theo tiêu chuẩn. Tôi hiểu những gì bạn nghĩ đang xảy ra; bạn chỉ xảy ra là sai "Trình hủy lớp cơ sở được gọi và nó hoạt động" là một triệu chứng có thể có (và phổ biến nhất) của hành vi không xác định này, bởi vì đó là mã máy ngây thơ mà trình biên dịch thường tạo ra. Điều này không làm cho nó an toàn cũng không phải là một ý tưởng tuyệt vời để làm.
Yakk - Adam Nevraumont

92

Toàn bộ STL được thiết kế theo cách các thuật toán và các thùng chứa riêng biệt .

Điều này dẫn đến một khái niệm về các loại trình vòng lặp khác nhau: các trình vòng lặp const, các trình vòng lặp truy cập ngẫu nhiên, v.v.

Do đó, tôi khuyên bạn nên chấp nhận quy ước này và thiết kế các thuật toán của bạn theo cách mà họ sẽ không quan tâm đến việc container đang hoạt động là gì - và họ sẽ chỉ yêu cầu một loại trình vòng lặp cụ thể mà họ cần để thực hiện hoạt động.

Ngoài ra, hãy để tôi chuyển hướng bạn đến một số nhận xét tốt của Jeff Attwood .


63

Lý do chính cho việc không kế thừa từ std::vectorcông khai là sự vắng mặt của một kẻ hủy diệt ảo ngăn cản bạn sử dụng con cháu một cách đa hình. Đặc biệt, bạn đang không được phép để deletemột std::vector<T>*mà thực sự điểm tại một đối tượng có nguồn gốc (ngay cả khi lớp được thừa kế không có thêm thành viên), nhưng trình biên dịch thường không thể cảnh báo bạn về nó.

Thừa kế tư nhân được cho phép trong các điều kiện này. Do đó, tôi khuyên bạn nên sử dụng kế thừa riêng và chuyển tiếp các phương thức cần thiết từ cha mẹ như dưới đây.

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> vector;
public:
    using vector::push_back;
    using vector::operator[];
    using vector::begin;
    using vector::end;
    AdVector operator*(const AdVector & ) const;
    AdVector operator+(const AdVector & ) const;
    AdVector();
    virtual ~AdVector();
};

Trước tiên, bạn nên xem xét tái cấu trúc các thuật toán của mình để trừu tượng loại container mà chúng đang hoạt động và để chúng dưới dạng các hàm templated miễn phí, như được chỉ ra bởi đa số người trả lời. Điều này thường được thực hiện bằng cách làm cho một thuật toán chấp nhận một cặp các vòng lặp thay vì chứa làm đối số.


IIUC, sự vắng mặt của một hàm hủy ảo chỉ là một vấn đề nếu lớp dẫn xuất phân bổ các tài nguyên phải được giải phóng khi hủy. (Chúng sẽ không được giải phóng trong trường hợp sử dụng đa hình vì bối cảnh vô tình chiếm quyền sở hữu đối tượng dẫn xuất qua con trỏ đến cơ sở sẽ chỉ gọi hàm hủy cơ sở khi đến lúc.) Các vấn đề tương tự phát sinh từ các hàm thành viên bị ghi đè khác, vì vậy phải cẩn thận được thực hiện rằng những người cơ sở là hợp lệ để gọi. Nhưng vắng mặt nguồn lực bổ sung, có bất kỳ lý do khác?
Peter - Tái lập Monica

2
vectorLưu trữ được phân bổ không phải là vấn đề - sau tất cả, hàm vectorhủy của nó sẽ được gọi hoàn toàn thông qua một con trỏ tới vector. Nó chỉ là sự cấm tiêu chuẩn deleteing cửa hàng miễn phí các đối tượng thông qua một biểu thức lớp cơ sở. Lý do chắc chắn là cơ chế phân bổ (de) có thể cố gắng suy ra kích thước của khối bộ nhớ để thoát khỏi deletetoán hạng, ví dụ như khi có một số đấu trường phân bổ cho các đối tượng có kích thước nhất định. Hạn chế này, afaics, không áp dụng cho việc phá hủy bình thường các đối tượng với thời gian lưu trữ tĩnh hoặc tự động.
Peter - Tái lập Monica

@DavisHerring Tôi nghĩ chúng tôi đồng ý ở đó :-).
Peter - Phục hồi Monica

@DavisHerring Ah, tôi hiểu rồi, bạn tham khảo bình luận đầu tiên của tôi - có một IIUC trong bình luận đó, và nó đã kết thúc bằng một câu hỏi; Tôi thấy sau đó thực sự nó luôn bị cấm. (Basilevs đã đưa ra một tuyên bố chung, "ngăn chặn hiệu quả" và tôi tự hỏi về cách thức cụ thể mà nó ngăn chặn.) Vì vậy, có, chúng tôi đồng ý: UB.
Peter - Tái lập Monica

@Basilevs Điều đó chắc hẳn đã vô tình. Đã sửa.
ThomasMcLeod

36

Nếu bạn đang xem xét điều này, rõ ràng bạn đã giết những người dạy ngôn ngữ trong văn phòng của bạn. Với họ hết cách, tại sao không làm

struct MyVector
{
   std::vector<Thingy> v;  // public!
   void func1( ... ) ; // and so on
}

Điều đó sẽ vượt qua tất cả các lỗi ngớ ngẩn có thể xảy ra do vô tình làm xáo trộn lớp MyVector của bạn và bạn vẫn có thể truy cập tất cả các vectơ chỉ bằng cách thêm một chút .v.


Và phơi bày container và thuật toán? Xem câu trả lời của Kos ở trên.
bruno nery


19

Bạn đang hy vọng đạt được điều gì? Chỉ cung cấp một số chức năng?

Cách thành ngữ C ++ để làm điều này là chỉ viết một số hàm miễn phí thực hiện chức năng. Có thể bạn không thực sự yêu cầu std :: vector, đặc biệt cho chức năng bạn đang triển khai, điều đó có nghĩa là bạn thực sự đang mất khả năng sử dụng lại bằng cách cố gắng kế thừa từ std :: vector.

Tôi đặc biệt khuyên bạn nên nhìn vào thư viện và tiêu đề tiêu chuẩn, và suy ngẫm về cách chúng hoạt động.


5
Tôi không tin chắc. Bạn có thể cập nhật với một số mã được đề xuất để giải thích tại sao không?
Karl Knechtel

6
@Armen: ngoài tính thẩm mỹ, được có bất kỳ tốt lý do?
snemarch

12
@Armen: Tính thẩm mỹ tốt hơn, và tính tổng quát cao hơn, sẽ là cung cấp miễn phí frontvà các backchức năng cũng vậy. :) (Cũng xem xét ví dụ về miễn phí beginendtrong C ++ 0x và boost.)
UncleBens

3
Tôi vẫn không có gì sai với các chức năng miễn phí. Nếu bạn không thích "tính thẩm mỹ" của STL, thì có lẽ C ++ không phù hợp với bạn, về mặt thẩm mỹ. Và việc thêm một số hàm thành viên sẽ không sửa được nó, vì nhiều thuật toán khác vẫn là các hàm miễn phí.
Frank Osterfeld

17
Thật khó để lưu trữ kết quả của hoạt động nặng trong một thuật toán bên ngoài. Giả sử bạn phải tính tổng của tất cả các phần tử trong vectơ hoặc để giải phương trình đa thức với các phần tử vectơ là hệ số. Những hoạt động đó là nặng nề và lười biếng sẽ hữu ích cho họ. Nhưng bạn không thể giới thiệu nó mà không gói hoặc kế thừa từ container.
Basilevs

14

Tôi nghĩ rằng rất ít quy tắc nên được tuân thủ một cách mù quáng 100% thời gian. Có vẻ như bạn đã suy nghĩ khá nhiều và tin chắc rằng đây là con đường để đi. Vì vậy - trừ khi ai đó đưa ra những lý do cụ thể tốt để không làm điều này - tôi nghĩ bạn nên tiếp tục với kế hoạch của mình.


9
Câu đầu tiên của bạn là đúng 100%. :)
Steve Fallows

5
Thật không may, câu thứ hai không. Anh ấy đã không suy nghĩ nhiều. Hầu hết các câu hỏi là không liên quan. Phần duy nhất của nó cho thấy động lực của anh ấy là "Tôi muốn họ là thành viên trực tiếp của vectơ". Tôi muốn. Không có lý do tại sao điều này là mong muốn. Nghe có vẻ như anh ta đã không nghĩ gì cả .
jalf

7

Không có lý do để kế thừa std::vectortrừ khi người ta muốn tạo ra một lớp hoạt động khác hơn std::vector, bởi vì nó xử lý theo cách riêng của nó các chi tiết ẩn của std::vectorđịnh nghĩa, hoặc trừ khi người ta có lý do ý thức hệ để sử dụng các đối tượng của lớp đó thay cho std::vectorNhững cái đó. Tuy nhiên, những người tạo ra tiêu chuẩn trên C ++ không cung cấp std::vectorbất kỳ giao diện nào (dưới dạng các thành viên được bảo vệ) mà lớp kế thừa đó có thể tận dụng để cải thiện vectơ theo cách cụ thể. Thật vậy, họ không có cách nào để nghĩ về bất kỳ khía cạnh cụ thể nào có thể cần mở rộng hoặc tinh chỉnh triển khai bổ sung, vì vậy họ không cần nghĩ đến việc cung cấp bất kỳ giao diện nào như vậy cho bất kỳ mục đích nào.

Các lý do cho tùy chọn thứ hai có thể chỉ là ý thức hệ, bởi vì std::vectorchúng không phải là đa hình, và nếu không thì không có sự khác biệt nào cho dù bạn phơi bày std::vectorgiao diện công khai thông qua thừa kế công khai hay thông qua thành viên công cộng. (Giả sử bạn cần giữ một số trạng thái trong đối tượng của mình để bạn không thể thoát khỏi các chức năng miễn phí). Trên một ghi chú ít âm thanh hơn và theo quan điểm ý thức hệ, có vẻ như đó std::vectorlà một loại "ý tưởng đơn giản", do đó, bất kỳ sự phức tạp nào ở dạng đối tượng của các lớp khác nhau có thể không sử dụng.


Câu trả lời chính xác. Chào mừng đến với SO!
Armen Tsirunyan

4

Trong điều kiện thực tế: Nếu bạn không có bất kỳ thành viên dữ liệu nào trong lớp dẫn xuất của mình, bạn không có bất kỳ vấn đề nào, ngay cả trong việc sử dụng đa hình. Bạn chỉ cần một hàm hủy ảo nếu kích thước của lớp cơ sở và lớp dẫn xuất khác nhau và / hoặc bạn có các hàm ảo (có nghĩa là bảng v).

Về mặt lý thuyết: Từ [expr.delete] trong C ++ 0x FCD: Trong phương án đầu tiên (xóa đối tượng), nếu loại tĩnh của đối tượng cần xóa khác với loại động của nó, loại tĩnh sẽ là một lớp cơ sở của kiểu động của đối tượng cần xóa và kiểu tĩnh sẽ có hàm hủy ảo hoặc hành vi không xác định.

Nhưng bạn có thể lấy riêng từ std :: vector mà không gặp vấn đề gì. Tôi đã sử dụng mẫu sau:

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...

3
"Bạn chỉ cần một hàm hủy ảo nếu kích thước của lớp cơ sở và lớp dẫn xuất khác nhau / hoặc bạn có các hàm ảo (có nghĩa là bảng v)." Yêu cầu này thực tế là đúng, nhưng về mặt lý thuyết
Armen Tsirunyan

2
vâng, về nguyên tắc nó vẫn là hành vi không xác định.
jalf

Nếu bạn cho rằng đây là hành vi không xác định, tôi muốn xem một bằng chứng (trích dẫn từ tiêu chuẩn).
hmuelner

8
@hmuelner: Thật không may, Armen và jalf đều đúng trong trường hợp này. Từ [expr.delete]trong C ++ 0x FCD: <quote> Trong phương án đầu tiên (xóa đối tượng), nếu loại tĩnh của đối tượng cần xóa khác với loại động của nó, loại tĩnh sẽ là lớp cơ sở của loại động của đối tượng cần xóa và loại tĩnh sẽ có hàm hủy ảo hoặc hành vi không xác định. </ quote>
Ben Voigt

1
Điều này thật buồn cười, bởi vì tôi thực sự nghĩ rằng hành vi phụ thuộc vào sự hiện diện của một hàm hủy không tầm thường (cụ thể là các lớp POD có thể bị phá hủy thông qua một con trỏ đến cơ sở).
Ben Voigt

3

Nếu bạn theo phong cách C ++ tốt, sự vắng mặt của chức năng ảo không phải là vấn đề, mà là cắt lát (xem https://stackoverflow.com/a/14461532/877329 )

Tại sao sự vắng mặt của các chức năng ảo không phải là vấn đề? Bởi vì một hàm không nên thử deletebất kỳ con trỏ nào mà nó nhận được, vì nó không có quyền sở hữu của nó. Do đó, nếu tuân theo các chính sách sở hữu nghiêm ngặt, không cần thiết phải hủy bỏ ảo. Ví dụ, điều này luôn sai (có hoặc không có hàm hủy ảo):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    delete obj;
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj); //Will crash here. But caller does not know that
//  ...
    }

Ngược lại, điều này sẽ luôn hoạt động (có hoặc không có hàm hủy ảo):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj);
//  The correct destructor *will* be called here.
    }

Nếu đối tượng được tạo bởi một nhà máy, nhà máy cũng nên trả về một con trỏ cho một deleter làm việc, nên được sử dụng thay vì delete, vì nhà máy có thể sử dụng heap của chính nó. Người gọi có thể nhận nó dưới dạng một share_ptrhoặc unique_ptr. Nói tóm lại, không deletebất cứ điều gì bạn đã không nhận được trực tiếp từ new.


2

Vâng, nó an toàn miễn là bạn cẩn thận không làm những điều không an toàn ... Tôi không nghĩ tôi từng thấy ai sử dụng một vectơ mới vì vậy trong thực tế, bạn có thể sẽ ổn. Tuy nhiên, nó không phải là thành ngữ phổ biến trong c ++ ....

Bạn có thể cung cấp thêm thông tin về các thuật toán là gì?

Đôi khi bạn kết thúc việc đi xuống một con đường với một thiết kế và sau đó không thể thấy những con đường khác mà bạn có thể đã đi - thực tế là bạn cho rằng cần phải vectơ với 10 thuật toán mới gióng lên hồi chuông cảnh báo cho tôi - thực sự có 10 mục đích chung các thuật toán mà một vectơ có thể thực hiện, hoặc bạn đang cố gắng tạo ra một đối tượng vừa là một vectơ mục đích chung VÀ chứa các hàm cụ thể của ứng dụng?

Tôi chắc chắn không nói rằng bạn không nên làm điều này, chỉ là với thông tin bạn đã phát ra tiếng chuông báo thức đang vang lên khiến tôi nghĩ rằng có thể có điều gì đó không ổn với sự trừu tượng của bạn và có một cách tốt hơn để đạt được những gì bạn muốn


2

Tôi cũng được thừa hưởng từ std::vectorgần đây và thấy nó rất hữu ích và cho đến nay tôi chưa gặp vấn đề gì với nó.

Lớp của tôi là một lớp ma trận thưa thớt, có nghĩa là tôi cần lưu trữ các phần tử ma trận của mình ở đâu đó, cụ thể là trong một std::vector. Lý do kế thừa của tôi là tôi hơi lười biếng khi viết giao diện cho tất cả các phương thức và tôi cũng đang giao tiếp lớp với Python thông qua SWIG, nơi đã có mã giao diện tốt std::vector. Tôi thấy việc mở rộng mã giao diện này đến lớp của tôi dễ dàng hơn nhiều so với việc viết một mã mới từ đầu.

Vấn đề duy nhất mà tôi có thể nhìn thấy bằng phương pháp này không phải là quá nhiều với destructor không ảo, mà đúng hơn là một số phương pháp khác, mà tôi muốn quá tải, chẳng hạn như push_back(), resize(), insert(), vv thừa kế cá nhân có thể thực sự là một lựa chọn tốt.

Cảm ơn!


10
Theo kinh nghiệm của tôi, thiệt hại lâu dài tồi tệ nhất thường là do những người cố gắng làm điều gì đó không đúng đắn và " cho đến nay vẫn chưa có kinh nghiệm (đọc chú ý ) bất kỳ vấn đề nào với nó".
vỡ mộng

0

Ở đây, hãy để tôi giới thiệu thêm 2 cách để làm bạn muốn. Một là một cách khác để bọc std::vector, một cách khác là kế thừa mà không cho người dùng cơ hội phá vỡ bất cứ điều gì:

  1. Hãy để tôi thêm một cách gói khác std::vectormà không cần viết nhiều hàm bao.

#include <utility> // For std:: forward
struct Derived: protected std::vector<T> {
    // Anything...
    using underlying_t = std::vector<T>;

    auto* get_underlying() noexcept
    {
        return static_cast<underlying_t*>(this);
    }
    auto* get_underlying() const noexcept
    {
        return static_cast<underlying_t*>(this);
    }

    template <class Ret, class ...Args>
    auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args)
    {
        return (get_underlying()->*member_f)(std::forward<Args>(args)...);
    }
};
  1. Kế thừa từ std :: span thay vì std::vectorvà tránh vấn đề dtor.

0

Câu hỏi này được đảm bảo để sản xuất ly hợp ngọc trai dễ thở, nhưng trên thực tế không có lý do phòng thủ nào để tránh, hoặc "các thực thể nhân lên không cần thiết" để tránh, xuất phát từ một thùng chứa Tiêu chuẩn. Biểu thức đơn giản nhất, ngắn nhất có thể là rõ ràng nhất và tốt nhất.

Bạn cần phải thực hiện tất cả các chăm sóc thông thường xung quanh bất kỳ loại dẫn xuất nào, nhưng không có gì đặc biệt về trường hợp của một cơ sở từ Tiêu chuẩn. Ghi đè một chức năng thành viên cơ sở có thể khó khăn, nhưng điều đó sẽ không khôn ngoan với bất kỳ cơ sở không ảo nào, vì vậy không có nhiều đặc biệt ở đây. Nếu bạn đã thêm một thành viên dữ liệu, bạn sẽ cần lo lắng về việc cắt nếu thành viên đó phải được giữ phù hợp với nội dung của cơ sở, nhưng một lần nữa điều đó cũng tương tự đối với bất kỳ cơ sở nào.

Nơi mà tôi đã tìm thấy xuất phát từ một thùng chứa tiêu chuẩn đặc biệt hữu ích là thêm một hàm tạo duy nhất thực hiện chính xác việc khởi tạo cần thiết, không có sự nhầm lẫn hoặc chiếm quyền điều khiển bởi các nhà xây dựng khác. (Tôi đang nhìn bạn, các nhà xây dựng initization_list!) Sau đó, bạn có thể tự do sử dụng đối tượng kết quả, cắt lát - chuyển nó bằng cách tham chiếu đến một cái gì đó mong đợi cơ sở, chuyển từ nó sang một thể hiện của cơ sở, bạn có gì. Không có trường hợp cạnh nào phải lo lắng, trừ khi điều đó làm phiền bạn liên kết một đối số khuôn mẫu với lớp dẫn xuất.

Một nơi mà kỹ thuật này sẽ hữu ích ngay lập tức trong C ++ 20 là đặt chỗ. Nơi chúng tôi có thể đã viết

  std::vector<T> names; names.reserve(1000);

chúng ta có thể nói

  template<typename C> 
  struct reserve_in : C { 
    reserve_in(std::size_t n) { this->reserve(n); }
  };

và sau đó, ngay cả khi là thành viên trong lớp,

  . . .
  reserve_in<std::vector<T>> taken_names{1000};  // 1
  std::vector<T> given_names{reserve_in<std::vector<T>>{1000}}; // 2
  . . .

(theo sở thích) và không cần phải viết một hàm tạo chỉ để gọi dự trữ () trên chúng.

(Lý do reserve_in, về mặt kỹ thuật, cần phải chờ C ++ 20 là vì các Tiêu chuẩn trước đó không yêu cầu khả năng của một vectơ trống được bảo toàn trong các lần di chuyển. Điều đó được coi là giám sát và có thể dự kiến ​​sẽ được sửa chữa như là một khiếm khuyết kịp thời cho năm 20. Chúng ta cũng có thể hy vọng bản sửa lỗi sẽ được hỗ trợ một cách hiệu quả theo các Tiêu chuẩn trước đó, bởi vì tất cả các triển khai hiện có thực sự bảo tồn năng lực qua các bước di chuyển; Các tiêu chuẩn không yêu cầu nó. súng - dự trữ gần như luôn luôn chỉ là một tối ưu hóa.)

Một số người sẽ cho rằng trường hợp reserve_inđược phục vụ tốt hơn bởi một mẫu hàm miễn phí:

  template<typename C> 
  auto reserve_in(std::size_t n) { C c; c.reserve(n); return c; }

Một sự thay thế như vậy chắc chắn là khả thi - và thậm chí, đôi khi, có thể nhanh hơn vô cùng, vì * RVO. Nhưng việc lựa chọn đạo hàm hoặc hàm tự do nên được thực hiện dựa trên giá trị riêng của nó, chứ không phải từ sự mê tín vô căn cứ (heh!) Về việc xuất phát từ các thành phần Tiêu chuẩn. Trong ví dụ sử dụng ở trên, chỉ có hình thức thứ hai sẽ hoạt động với chức năng miễn phí; mặc dù bên ngoài bối cảnh lớp học, nó có thể được viết ngắn gọn hơn một chút:

  auto given_names{reserve_in<std::vector<T>>(1000)}; // 2
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.