Đối tượng cắt là gì?


Câu trả lời:


609

"Cắt lát" là nơi bạn gán một đối tượng của lớp dẫn xuất cho một thể hiện của lớp cơ sở, do đó làm mất một phần thông tin - một số thông tin bị "cắt" đi.

Ví dụ,

class A {
   int foo;
};

class B : public A {
   int bar;
};

Vì vậy, một đối tượng của loại Bcó hai thành viên dữ liệu foobar.

Sau đó, nếu bạn đã viết này:

B b;

A a = b;

Sau đó, thông tin bvề thành viên barbị mất trong a.


66
Rất nhiều thông tin, nhưng hãy xem stackoverflow.com/questions/274626#274636 để biết ví dụ về cách cắt lát xảy ra trong các cuộc gọi phương thức (điều này nhấn mạnh sự nguy hiểm tốt hơn một chút so với ví dụ gán đơn giản).
Blair Conrad

55
Hấp dẫn. Tôi đã lập trình trong C ++ được 15 năm và vấn đề này chưa bao giờ xảy ra với tôi, vì tôi luôn vượt qua các đối tượng bằng cách tham khảo như một vấn đề hiệu quả và phong cách cá nhân. Cho thấy những thói quen tốt có thể giúp bạn như thế nào.
Karl Bielefeldt

10
@Felix Cảm ơn nhưng tôi không nghĩ rằng việc truyền lại (vì không phải là số học con trỏ) sẽ hoạt động, A a = b; abây giờ là đối tượng của loại Acó bản sao B::foo. Tôi nghĩ sẽ là sai lầm khi bỏ nó lại bây giờ.

37
Đây không phải là "cắt", hoặc ít nhất là một biến thể lành tính của nó. Vấn đề thực sự xảy ra nếu bạn làm B b1; B b2; A& b2_ref = b2; b2 = b1. Bạn có thể nghĩ rằng bạn đã sao chép b1vào b2, nhưng bạn đã không! Bạn đã sao chép một phần của b1để b2(một phần của b1Bthừa hưởng từ A), và để lại các phần khác của b2không thay đổi. b2bây giờ là một sinh vật frankenstein bao gồm một vài bit b1theo sau bởi một số khối b2. Ừ! Downvote vì tôi nghĩ rằng câu trả lời là rất sai.
fgp

24
@fgp Nhận xét của bạn nên đọc B b1; B b2; A& b2_ref = b2; b2_ref = b1" Vấn đề thực sự xảy ra nếu bạn " ... xuất phát từ một lớp có toán tử gán không ảo. AThậm chí là dự định cho phái sinh? Nó không có chức năng ảo. Nếu bạn xuất phát từ một loại, bạn phải đối phó với thực tế là các hàm thành viên của nó có thể được gọi!
tò mò

510

Hầu hết các câu trả lời ở đây không giải thích được vấn đề thực sự với việc cắt lát là gì. Họ chỉ giải thích các trường hợp lành tính của cắt lát, không phải là những người phản bội. Giả sử, giống như các câu trả lời khác, rằng bạn đang giao dịch với hai lớp AB, Bxuất phát (công khai) từ đâu A.

Trong tình huống này, C ++ cho phép bạn vượt qua một thể hiện của Bđể A's toán tử gán (và cũng để các nhà xây dựng bản sao). Điều này hoạt động vì một thể hiện của Bcó thể được chuyển đổi thành a const A&, đó là điều mà các toán tử gán và các nhà xây dựng sao chép mong đợi các đối số của chúng là.

Các trường hợp lành tính

B b;
A a = b;

Không có gì xấu xảy ra ở đó - bạn đã yêu cầu một ví dụ trong Ađó là bản sao Bvà đó chính xác là những gì bạn nhận được. Chắc chắn, asẽ không chứa một số bthành viên, nhưng làm thế nào? Rốt cuộc A, đó không phải là một B, vì vậy thậm chí còn chưa từng nghe về những thành viên này, nói gì đến việc có thể lưu trữ chúng.

Vụ án phản bội

B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!

Bạn có thể nghĩ rằng đó b2sẽ là một bản sao b1sau đó. Nhưng, than ôi, không phải vậy! Nếu bạn kiểm tra nó, bạn sẽ phát hiện ra đó b2là một sinh vật Frankenstein, được tạo ra từ một số khối b1(khối Bđược thừa hưởng từ A) và một số khối b2(khối chỉ Bchứa). Ôi!

Chuyện gì đã xảy ra? Chà, theo mặc định, C ++ không coi các toán tử gán như virtual. Do đó, dòng a_ref = b1sẽ gọi toán tử gán A, không phải của B. Điều này là do, đối với các hàm không ảo, loại khai báo (chính thức: tĩnh ) A&xác định hàm nào được gọi, trái ngược với loại thực tế (chính thức: động ) (sẽ là B, vì a_reftham chiếu một thể hiện của B) . Bây giờ, Atoán tử gán chỉ rõ ràng chỉ biết về các thành viên được khai báo A, vì vậy nó sẽ chỉ sao chép các thành viên đó, khiến các thành viên được thêm vào Bkhông thay đổi.

Một giải pháp

Chỉ gán cho các bộ phận của một đối tượng thường không có ý nghĩa gì, nhưng thật không may, C ++ không cung cấp cách tích hợp nào để cấm điều này. Bạn có thể, tuy nhiên, cuộn của riêng bạn. Bước đầu tiên là làm cho toán tử gán gán ảo . Điều này sẽ đảm bảo rằng nó luôn là toán tử gán của loại thực tế được gọi, không phải là kiểu khai báo . Bước thứ hai là sử dụng dynamic_castđể xác minh rằng đối tượng được gán có loại tương thích. Bước thứ ba là để làm việc chuyển nhượng thực tế trong một thành viên (được bảo vệ!) assign(), Vì B's assign()có thể sẽ muốn sử dụng Aassign()để sao chép A's, các thành viên.

class A {
public:
  virtual A& operator= (const A& a) {
    assign(a);
    return *this;
  }

protected:
  void assign(const A& a) {
    // copy members of A from a to this
  }
};

class B : public A {
public:
  virtual B& operator= (const A& a) {
    if (const B* b = dynamic_cast<const B*>(&a))
      assign(*b);
    else
      throw bad_assignment();
    return *this;
  }

protected:
  void assign(const B& b) {
    A::assign(b); // Let A's assign() copy members of A from b to this
    // copy members of B from b to this
  }
};

Lưu ý rằng, để thuận tiện trong sạch, Boperator=covariantly đè kiểu trả về, vì nó biết rằng nó đang trở về một thể hiện của B.


12
IMHO, vấn đề là có hai loại thay thế khác nhau có thể được ngụ ý bởi sự kế thừa: bất kỳ derivedgiá trị nào cũng có thể được trao cho mã mong đợi một basegiá trị, hoặc bất kỳ tham chiếu dẫn xuất nào cũng có thể được sử dụng làm tham chiếu cơ sở. Tôi muốn thấy một ngôn ngữ với một hệ thống loại giải quyết cả hai khái niệm riêng biệt. Có nhiều trường hợp trong đó một tham chiếu dẫn xuất nên có thể thay thế cho một tham chiếu cơ sở, nhưng các trường hợp dẫn xuất không nên thay thế cho các tham chiếu cơ sở; cũng có nhiều trường hợp các trường hợp nên chuyển đổi nhưng tài liệu tham khảo không nên thay thế.
supercat

16
Tôi không hiểu điều gì là xấu trong trường hợp "phản bội" của bạn. Bạn đã nói rằng bạn muốn: 1) lấy tham chiếu đến một đối tượng của lớp A và 2) chuyển đối tượng b1 sang lớp A và sao chép nội dung của nó vào một tham chiếu của lớp A. Điều thực sự sai ở đây là logic đúng đằng sau mã đã cho. Nói cách khác, bạn đã chụp một khung hình nhỏ (A), đặt nó lên một hình ảnh lớn hơn (B) và bạn đã vẽ qua khung đó, sau đó phàn nàn rằng hình ảnh lớn hơn của bạn bây giờ trông xấu xí :) Nhưng nếu chúng ta chỉ xem xét khu vực có khung đó, Nó trông khá tốt, giống như họa sĩ muốn, phải không? :)
Mladen B.

12
Vấn đề là, theo cách khác, C ++ theo mặc định giả định một loại thay thế rất mạnh - nó đòi hỏi các hoạt động của lớp cơ sở phải hoạt động chính xác trên các thể hiện của lớp con. Và ngay cả đối với các hoạt động mà trình biên dịch tự động giống như gán. Vì vậy, nó không đủ để không làm hỏng các hoạt động của riêng bạn về vấn đề này, bạn cũng phải vô hiệu hóa một cách rõ ràng những cái sai do trình biên dịch tạo ra. Hoặc tất nhiên, tránh xa sự kế thừa công khai, thường là một gợi ý tốt về anway ;-)
fgp 16/11/13

14
Một cách tiếp cận phổ biến khác là chỉ cần vô hiệu hóa toán tử sao chép và gán. Đối với các lớp trong hệ thống phân cấp thừa kế, thường không có lý do để sử dụng giá trị thay vì tham chiếu hoặc con trỏ.
Siyuan Ren

13
Cái gì? Tôi không có toán tử ý tưởng nào có thể được đánh dấu ảo
paulm

154

Nếu bạn có một lớp cơ sở Avà một lớp dẫn xuất B, thì bạn có thể làm như sau.

void wantAnA(A myA)
{
   // work with myA
}

B derived;
// work with the object "derived"
wantAnA(derived);

Bây giờ phương thức wantAnAcần một bản sao của derived. Tuy nhiên, đối tượng derivedkhông thể được sao chép hoàn toàn, vì lớp Bcó thể phát minh ra các biến thành viên bổ sung không có trong lớp cơ sở của nó A.

Do đó, để gọi wantAnA, trình biên dịch sẽ "cắt" tất cả các thành viên bổ sung của lớp dẫn xuất. Kết quả có thể là một đối tượng bạn không muốn tạo, bởi vì

  • nó có thể không đầy đủ
  • nó hoạt động như một A-object (tất cả các hành vi đặc biệt của lớp Bbị mất).

41
C ++ không phải là Java! Nếu wantAnA(như tên của nó ngụ ý!) Muốn có A, thì đó là những gì nó nhận được. Và một ví dụ A, sẽ, uh, hành xử như một A. Làm thế nào là đáng ngạc nhiên?
fgp

83
@fgp: Thật đáng ngạc nhiên, vì bạn không chuyển A cho hàm.
Đen

10
@fgp: Hành vi tương tự. Tuy nhiên, đối với lập trình viên C ++ trung bình thì có thể ít rõ ràng hơn. Theo như tôi hiểu câu hỏi, không ai "phàn nàn". Nó chỉ là về cách trình biên dịch xử lý tình huống. Imho, tốt hơn là tránh cắt lát bằng cách chuyển các tham chiếu (const).
Đen

9
@ThomasW Không, tôi sẽ không từ bỏ thừa kế, nhưng sử dụng tài liệu tham khảo. Nếu chữ ký của WantAnA sẽ bị vô hiệu hóa muốnAnA (const A & myA) , thì đã không bị cắt. Thay vào đó, một tham chiếu chỉ đọc đến đối tượng của người gọi được thông qua.
Đen

14
vấn đề chủ yếu nằm ở việc truyền tự động mà trình biên dịch thực hiện từ derivedloại A. Đúc ngầm định luôn là nguồn gốc của hành vi không mong muốn trong C ++, bởi vì thường rất khó hiểu khi nhìn vào mã cục bộ mà một diễn viên đã diễn ra.
pqnet

41

Đây là tất cả các câu trả lời tốt. Tôi chỉ muốn thêm một ví dụ thực thi khi chuyển các đối tượng theo giá trị so với tham chiếu:

#include <iostream>

using namespace std;

// Base class
class A {
public:
    A() {}
    A(const A& a) {
        cout << "'A' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am an 'A'" << endl; }
};

// Derived class
class B: public A {
public:
    B():A() {}
    B(const B& a):A(a) {
        cout << "'B' copy constructor" << endl;
    }
    virtual void run() const { cout << "I am a 'B'" << endl; }
};

void g(const A & a) {
    a.run();
}

void h(const A a) {
    a.run();
}

int main() {
    cout << "Call by reference" << endl;
    g(B());
    cout << endl << "Call by copy" << endl;
    h(B());
}

Đầu ra là:

Call by reference
I am a 'B'

Call by copy
'A' copy constructor
I am an 'A'

Xin chào. Câu trả lời tuyệt vời nhưng tôi có một câu hỏi. Nếu tôi làm một cái gì đó như thế này ** dev d; cơ sở * b = & d; ** Việc cắt lát cũng diễn ra?
Adrian

@Adrian Nếu bạn giới thiệu một số hàm thành viên mới hoặc các biến thành viên trong lớp dẫn xuất thì chúng không thể truy cập trực tiếp từ con trỏ lớp cơ sở. Tuy nhiên, bạn vẫn có thể truy cập chúng từ bên trong các hàm ảo lớp cơ sở quá tải. Xem điều này: godbolt.org/z/LABx33
Vishal Sharma

30

Trận đấu thứ ba trong google cho "C ++ cắt" cung cấp cho tôi bài viết Wikipedia này http://en.wikipedia.org/wiki/Object_slicing và điều này (nóng, nhưng một vài bài đăng đầu tiên xác định vấn đề): http://bytes.com/ diễn đàn / chủ đề163565.html

Vì vậy, đó là khi bạn gán một đối tượng của một lớp con cho siêu lớp. Siêu lớp không biết gì về thông tin bổ sung trong lớp con và không có chỗ để lưu trữ, vì vậy thông tin bổ sung sẽ bị "cắt".

Nếu các liên kết đó không cung cấp đủ thông tin cho "câu trả lời tốt", vui lòng chỉnh sửa câu hỏi của bạn để cho chúng tôi biết bạn đang tìm kiếm gì hơn.


29

Vấn đề cắt lát là nghiêm trọng vì nó có thể dẫn đến hỏng bộ nhớ và rất khó để đảm bảo chương trình không bị ảnh hưởng. Để thiết kế ngôn ngữ này, chỉ có thể truy cập các lớp hỗ trợ kế thừa bằng cách tham chiếu (không phải theo giá trị). Ngôn ngữ lập trình D có thuộc tính này.

Xem xét lớp A và lớp B xuất phát từ A. Tham nhũng bộ nhớ có thể xảy ra nếu phần A có con trỏ p và đối tượng B trỏ p đến dữ liệu bổ sung của B. Sau đó, khi dữ liệu bổ sung bị cắt, p sẽ trỏ đến rác.


3
Hãy giải thích làm thế nào tham nhũng bộ nhớ có thể xảy ra.
trả trước

4
Tôi quên rằng ctor sao chép sẽ thiết lập lại vptr, lỗi của tôi. Nhưng bạn vẫn có thể bị tham nhũng nếu A có con trỏ và B đặt điểm đó vào phần B bị cắt.
Walter Bright

18
Vấn đề này không chỉ giới hạn ở việc cắt lát. Bất kỳ lớp nào chứa con trỏ sẽ có hành vi đáng ngờ với toán tử gán mặc định và hàm tạo sao chép.
Weeble

2
@ Weeble - Đó là lý do tại sao bạn ghi đè hàm hủy, toán tử gán và hàm tạo sao chép mặc định trong các trường hợp này.
Bjarke Freund-Hansen

7
@ Weeble: Điều làm cho việc cắt đối tượng trở nên tồi tệ hơn so với sửa lỗi con trỏ chung là để chắc chắn rằng bạn đã ngăn chặn việc cắt lát xảy ra, một lớp cơ sở phải cung cấp các hàm tạo chuyển đổi cho mỗi lớp dẫn xuất . (Tại sao? Bất kỳ lớp dẫn xuất nào bị bỏ qua đều dễ bị chọn bởi bộ sao chép của lớp cơ sở, vì Derivednó hoàn toàn có thể chuyển đổi thành Base.) Điều này rõ ràng là đối nghịch với Nguyên tắc Đóng mở và gánh nặng bảo trì lớn.
j_random_hacker

11

Trong C ++, một đối tượng lớp dẫn xuất có thể được gán cho một đối tượng lớp cơ sở, nhưng cách khác là không thể.

class Base { int x, y; };

class Derived : public Base { int z, w; };

int main() 
{
    Derived d;
    Base b = d; // Object Slicing,  z and w of d are sliced off
}

Cắt đối tượng xảy ra khi một đối tượng lớp dẫn xuất được gán cho một đối tượng lớp cơ sở, các thuộc tính bổ sung của đối tượng lớp dẫn xuất được cắt ra để tạo thành đối tượng lớp cơ sở.


8

Vấn đề cắt trong C ++ phát sinh từ ngữ nghĩa giá trị của các đối tượng của nó, chủ yếu vẫn là do khả năng tương thích với các cấu trúc C. Bạn cần sử dụng tham chiếu rõ ràng hoặc cú pháp con trỏ để đạt được hành vi đối tượng "bình thường" được tìm thấy trong hầu hết các ngôn ngữ khác làm đối tượng, tức là, các đối tượng luôn được truyền qua xung quanh bởi tham chiếu.

Các câu trả lời ngắn gọn là bạn cắt đối tượng bằng cách gán một đối tượng dẫn xuất cho một đối tượng cơ sở theo giá trị , tức là đối tượng còn lại chỉ là một phần của đối tượng dẫn xuất. Để duy trì ngữ nghĩa giá trị, cắt lát là một hành vi hợp lý và có những cách sử dụng tương đối hiếm, không tồn tại trong hầu hết các ngôn ngữ khác. Một số người coi nó là một tính năng của C ++, trong khi nhiều người coi nó là một trong những điều kỳ quặc / không phù hợp với C ++.


5
" Hành vi đối tượng" bình thường " không phải là" hành vi đối tượng bình thường ", đó là ngữ nghĩa tham chiếu . Và nó không liên quan đến C struct, khả năng tương thích hoặc không có ý nghĩa khác mà bất kỳ linh mục OOP ngẫu nhiên nào nói với bạn.
tò mò

4
@cantlyguy Amen, anh trai. Thật buồn khi thấy C ++ thường xuyên bị bash vì không phải là Java, khi ngữ nghĩa giá trị là một trong những điều khiến C ++ cực kỳ mạnh mẽ.
fgp

Đây không phải là một tính năng, không phải là một sự châm biếm / không phù hợp. Đó là hành vi sao chép trên ngăn xếp bình thường, vì việc gọi một hàm với biến kiểu phân bổ arg hoặc (cùng) Basephải lấy chính xác sizeof(Base)byte trong bộ nhớ, với sự liên kết có thể, có thể, đó là lý do tại sao "gán" (sao chép trên ngăn xếp ) sẽ không sao chép các thành viên lớp dẫn xuất, phần bù của chúng nằm ngoài sizeof. Để tránh "mất dữ liệu", chỉ cần sử dụng con trỏ, giống như bất kỳ ai khác, vì bộ nhớ con trỏ được cố định về vị trí và kích thước, trong khi ngăn xếp rất dễ bay hơi
Croll

Chắc chắn là một hành vi sai trái của C ++. Việc chỉ định một đối tượng dẫn xuất cho một đối tượng cơ sở nên bị cấm, trong khi ràng buộc một đối tượng dẫn xuất với một tham chiếu hoặc một con trỏ của lớp cơ sở sẽ ổn.
John Z. Li

7

Vậy ... Tại sao mất thông tin dẫn xuất là xấu? ... bởi vì tác giả của lớp dẫn xuất có thể đã thay đổi cách biểu diễn sao cho việc cắt bỏ thông tin bổ sung sẽ thay đổi giá trị được đại diện bởi đối tượng. Điều này có thể xảy ra nếu lớp dẫn xuất nếu được sử dụng để lưu trữ một biểu diễn hiệu quả hơn cho các hoạt động nhất định, nhưng tốn kém để chuyển đổi trở lại biểu diễn cơ sở.

Cũng nghĩ rằng ai đó cũng nên đề cập đến những gì bạn nên làm để tránh cắt lát ... Nhận bản sao Tiêu chuẩn mã hóa C ++, 101 hướng dẫn quy tắc và thực tiễn tốt nhất. Đối phó với cắt lát là # 54.

Nó gợi ý một mô hình hơi phức tạp để giải quyết hoàn toàn vấn đề: có một trình tạo bản sao được bảo vệ, một DoClone thuần được bảo vệ và một bản sao công khai với một xác nhận sẽ cho bạn biết nếu một lớp dẫn xuất (xa hơn) không thực hiện đúng DoClone. (Phương pháp Clone tạo một bản sao sâu thích hợp của đối tượng đa hình.)

Bạn cũng có thể đánh dấu hàm tạo sao chép trên cơ sở rõ ràng cho phép cắt lát rõ ràng nếu muốn.


3
" Bạn cũng có thể đánh dấu hàm tạo sao chép trên cơ sở rõ ràng " không giúp ích gì cả.
tò mò

6

1. ĐỊNH NGH OFA CỦA VẤN ĐỀ SLICING

Nếu D là lớp dẫn xuất của lớp cơ sở B, thì bạn có thể gán một đối tượng có kiểu Xuất phát cho một biến (hoặc tham số) của kiểu Base.

THÍ DỤ

class Pet
{
 public:
    string name;
};
class Dog : public Pet
{
public:
    string breed;
};

int main()
{   
    Dog dog;
    Pet pet;

    dog.name = "Tommy";
    dog.breed = "Kangal Dog";
    pet = dog;
    cout << pet.breed; //ERROR

Mặc dù việc chuyển nhượng trên được cho phép, giá trị được gán cho vật nuôi biến sẽ mất trường giống của nó. Đây được gọi là vấn đề cắt lát .

2. CÁCH CỐ ĐỊNH VẤN ĐỀ SLICING

Để đánh bại vấn đề, chúng tôi sử dụng các con trỏ tới các biến động.

THÍ DỤ

Pet *ptrP;
Dog *ptrD;
ptrD = new Dog;         
ptrD->name = "Tommy";
ptrD->breed = "Kangal Dog";
ptrP = ptrD;
cout << ((Dog *)ptrP)->breed; 

Trong trường hợp này, không có thành viên dữ liệu hoặc hàm thành viên nào của biến động được chỉ ra bởi ptrD (đối tượng lớp con cháu) sẽ bị mất. Ngoài ra, nếu bạn cần sử dụng các chức năng, chức năng phải là một chức năng ảo.


7
Tôi hiểu phần "cắt", nhưng tôi không hiểu "vấn đề". Làm thế nào một vấn đề mà một số trạng thái dogkhông phải là một phần của lớp Pet( breedthành viên dữ liệu) không được sao chép trong biến pet? Mã này chỉ quan tâm đến các Petthành viên dữ liệu - rõ ràng. Cắt lát chắc chắn là một "vấn đề" nếu không mong muốn, nhưng tôi không thấy điều đó ở đây.
tò mò

4
" ((Dog *)ptrP)" Tôi đề nghị sử dụngstatic_cast<Dog*>(ptrP)
tò mò

Tôi đề nghị chỉ ra rằng bạn sẽ làm cho chuỗi 'giống' cuối cùng bị rò rỉ bộ nhớ mà không có hàm hủy ảo (hàm hủy của 'chuỗi' sẽ không được gọi) khi xóa qua 'ptrP' ... Tại sao điều bạn thể hiện có vấn đề? Các sửa chữa chủ yếu là thiết kế lớp thích hợp. Vấn đề trong trường hợp này là việc viết ra các hàm tạo để kiểm soát mức độ hiển thị khi kế thừa là tẻ nhạt và dễ bị lãng quên. Bạn sẽ không nhận được bất cứ nơi nào gần khu vực nguy hiểm với mã của bạn vì không có sự đa hình liên quan hoặc thậm chí được đề cập (cắt sẽ cắt ngắn đối tượng của bạn nhưng không làm cho chương trình của bạn bị sập, ở đây).
Dude

24
-1 Điều này hoàn toàn không giải thích được vấn đề thực tế. C ++ có ngữ nghĩa giá trị, không phải ngữ nghĩa tham chiếu như Java, vì vậy đây hoàn toàn là điều được mong đợi. Và "sửa chữa" thực sự là một ví dụ về mã C ++ thực sự khủng khiếp . "Khắc phục" các vấn đề không tồn tại như kiểu cắt lát này bằng cách sử dụng phân bổ động là một công thức cho mã lỗi, bộ nhớ bị rò rỉ và hiệu suất khủng khiếp. Lưu ý rằng có những trường hợp cắt lát là xấu, nhưng câu trả lời này không chỉ ra chúng. Gợi ý: rắc rối bắt đầu nếu bạn chỉ định thông qua các tài liệu tham khảo .
fgp

Bạn thậm chí có hiểu rằng cố gắng truy cập thành viên của loại không được xác định ( Dog::breed) không có cách nào L ERI liên quan đến SLICING không?
Croll

4

Dường như đối với tôi, việc cắt lát không phải là vấn đề quá lớn ngoài khi các lớp học và chương trình của bạn được thiết kế / thiết kế kém.

Nếu tôi truyền một đối tượng lớp con dưới dạng tham số cho một phương thức, trong đó có tham số kiểu siêu lớp, tôi chắc chắn sẽ nhận thức được điều đó và biết bên trong, phương thức được gọi sẽ chỉ làm việc với đối tượng siêu lớp (còn gọi là lớp cơ sở).

Đối với tôi, dường như chỉ có sự kỳ vọng không hợp lý rằng việc cung cấp một lớp con nơi yêu cầu một lớp cơ sở, bằng cách nào đó sẽ dẫn đến kết quả cụ thể của lớp con, sẽ khiến cho việc cắt lát trở thành một vấn đề. Thiết kế của nó kém trong việc sử dụng phương thức hoặc triển khai lớp con kém. Tôi đoán nó thường là kết quả của việc hy sinh thiết kế OOP tốt để ủng hộ hiệu quả hoặc tăng hiệu suất.


3
Nhưng hãy nhớ, Minok, rằng bạn KHÔNG chuyển qua một tài liệu tham khảo về đối tượng đó. Bạn đang chuyển một bản sao MỚI của đối tượng đó, nhưng sử dụng lớp cơ sở để sao chép nó trong quy trình.
Arafangion

bản sao / bài tập được bảo vệ trên lớp cơ sở và vấn đề này được giải quyết.
Dude

1
Bạn đúng. Thực hành tốt là sử dụng các lớp cơ sở trừu tượng hoặc để hạn chế quyền truy cập vào sao chép / gán. Tuy nhiên, không dễ để nhận ra một khi nó ở đó và dễ quên để chăm sóc. Gọi các phương thức ảo bằng lát cắt * điều này có thể khiến những điều bí ẩn xảy ra nếu bạn thoát khỏi mà không vi phạm quyền truy cập.
Dude

1
Tôi nhớ lại từ các khóa học lập trình C ++ của mình ở trường đại học rằng có những thực tiễn tốt nhất mà mỗi lớp chúng tôi tạo ra, chúng tôi được yêu cầu viết các hàm tạo mặc định, sao chép hàm tạo và toán tử gán, cũng như hàm hủy. Bằng cách này, bạn đã chắc chắn rằng việc xây dựng bản sao và những thứ tương tự đã xảy ra theo cách bạn cần, trong khi viết lớp ... thay vì sau đó về một số hành vi kỳ quặc xuất hiện.
Minok

3

OK, tôi sẽ thử sau khi đọc nhiều bài viết giải thích việc cắt đối tượng nhưng không làm thế nào nó trở nên có vấn đề.

Kịch bản xấu xa có thể dẫn đến tham nhũng bộ nhớ là như sau:

  • Lớp cung cấp (vô tình, có thể là do trình biên dịch tạo) trên một lớp cơ sở đa hình.
  • Khách hàng sao chép và cắt một thể hiện của một lớp dẫn xuất.
  • Máy khách gọi một hàm thành viên ảo truy cập trạng thái cắt lát.

3

Cắt lát có nghĩa là dữ liệu được thêm bởi một lớp con bị loại bỏ khi một đối tượng của lớp con được truyền hoặc trả về bởi giá trị hoặc từ một hàm mong đợi một đối tượng lớp cơ sở.

Giải thích: Xem xét khai báo lớp sau:

           class baseclass
          {
                 ...
                 baseclass & operator =(const baseclass&);
                 baseclass(const baseclass&);
          }
          void function( )
          {
                baseclass obj1=m;
                obj1=m;
          }

Vì các hàm sao chép cơ sở không biết gì về phần dẫn xuất, chỉ phần cơ sở của phần dẫn xuất được sao chép. Điều này thường được gọi là cắt lát.


1
class A 
{ 
    int x; 
};  

class B 
{ 
    B( ) : x(1), c('a') { } 
    int x; 
    char c; 
};  

int main( ) 
{ 
    A a; 
    B b; 
    a = b;     // b.c == 'a' is "sliced" off
    return 0; 
}

4
Bạn có phiền cho thêm một số chi tiết? Làm thế nào để câu trả lời của bạn khác với những người đã đăng?
Alexis Pigeon

2
Tôi đoán rằng giải thích nhiều hơn sẽ không phải là xấu.
looper

-1

khi một đối tượng lớp dẫn xuất được gán cho một đối tượng lớp cơ sở, các thuộc tính bổ sung của đối tượng lớp dẫn xuất được cắt ra (loại bỏ) tạo thành đối tượng lớp cơ sở.

class Base { 
int x;
 };

class Derived : public Base { 
 int z; 
 };

 int main() 
{
Derived d;
Base b = d; // Object Slicing,  z of d is sliced off
}

-1

Khi một đối tượng lớp Derogen được gán cho Đối tượng lớp cơ sở, tất cả các thành viên của đối tượng lớp dẫn xuất được sao chép vào đối tượng lớp cơ sở ngoại trừ các thành viên không có trong lớp cơ sở. Các thành viên này được cắt bởi trình biên dịch. Điều này được gọi là cắt lát đối tượng.

Đây là một ví dụ:

#include<bits/stdc++.h>
using namespace std;
class Base
{
    public:
        int a;
        int b;
        int c;
        Base()
        {
            a=10;
            b=20;
            c=30;
        }
};
class Derived : public Base
{
    public:
        int d;
        int e;
        Derived()
        {
            d=40;
            e=50;
        }
};
int main()
{
    Derived d;
    cout<<d.a<<"\n";
    cout<<d.b<<"\n";
    cout<<d.c<<"\n";
    cout<<d.d<<"\n";
    cout<<d.e<<"\n";


    Base b = d;
    cout<<b.a<<"\n";
    cout<<b.b<<"\n";
    cout<<b.c<<"\n";
    cout<<b.d<<"\n";
    cout<<b.e<<"\n";
    return 0;
}

Nó sẽ tạo ra:

[Error] 'class Base' has no member named 'd'
[Error] 'class Base' has no member named 'e'

Bị từ chối vì đó không phải là một ví dụ tốt. Nó sẽ không hoạt động nếu thay vì sao chép d sang b, bạn sẽ sử dụng một con trỏ trong trường hợp d và e vẫn tồn tại nhưng Base không có các thành viên đó. Ví dụ của bạn chỉ cho thấy rằng bạn không thể truy cập các thành viên mà lớp không có.
Stefan Fabian

-2

Tôi chỉ chạy qua vấn đề cắt lát và nhanh chóng hạ cánh ở đây. Vì vậy, hãy để tôi thêm hai xu của tôi vào đây.

Chúng ta hãy lấy một ví dụ từ "mã sản xuất" (hoặc một cái gì đó gần giống):


Hãy nói rằng chúng ta có một cái gì đó gửi hành động. Một UI trung tâm điều khiển chẳng hạn.
UI này cần có được một danh sách những thứ hiện có thể được gửi đi. Vì vậy, chúng tôi định nghĩa một lớp có chứa thông tin công văn. Hãy gọi nó là Action. Vì vậy, an Actioncó một số biến thành viên. Để đơn giản, chúng ta chỉ có 2, là a std::string namevà a std::function<void()> f. Sau đó, nó có một void activate()cái mà chỉ thực hiện các fthành viên.

Vì vậy, UI được std::vector<Action>cung cấp. Hãy tưởng tượng một số chức năng như:

void push_back(Action toAdd);

Bây giờ chúng tôi đã thiết lập giao diện của giao diện người dùng. Không có vấn đề cho đến nay. Nhưng một số anh chàng khác làm việc trong dự án này đột nhiên quyết định rằng có những hành động chuyên biệt cần thêm thông tin trong Actionđối tượng. Vì lý do gì bao giờ. Điều đó cũng có thể được giải quyết với các vụ bắt giữ lambda. Ví dụ này không được lấy 1-1 từ mã.

Vì vậy, anh chàng xuất phát từ Actionđể thêm hương vị của riêng mình.
Anh ta chuyển một ví dụ của lớp học pha chế tại nhà của mình cho push_backnhưng sau đó chương trình trở nên hay.

Vậy chuyện gì đã xảy ra?
Như bạn có thể đoán: đối tượng đã bị cắt.

Thông tin bổ sung từ ví dụ đã bị mất và fhiện có xu hướng không xác định hành vi.


Tôi hy vọng ví dụ này mang lại ánh sáng cho những người thực sự không thể tưởng tượng mọi thứ khi nói về As và Bs được bắt nguồn theo một cách nào đó.

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.