Cắt đối tượng là gì?

Nov 08 2008

Ai đó đã đề cập đến nó trong IRC như là vấn đề cắt.

Trả lời

635 DavidDibben Nov 08 2008 at 18:22

"Slicing" 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 kiểu Bcó hai thành viên dữ liệu, foobar.

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

B b;

A a = b;

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

530 fgp Jan 22 2013 at 22:00

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 những trường hợp lành tính của vết cắt chứ không phải những trường hợp nguy hiểm. Giả sử, giống như các câu trả lời khác, rằng bạn đang xử lý hai lớp AB, Bnguồn gốc (công khai) từ đâu A.

Trong trường hợp này, C ++ cho phép bạn chuyển một thể hiện của toán tử gán Bcho A'(và cả phương thức tạo 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 hàm tạo bản sao mong đợi các đối số của chúng.

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 Amột bản sao của nó B, và đó 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 để nó? Rốt cuộc là Akhông phải a B, cho nên nó còn chưa nghe nói về những thành viên này, huống chi là có thể lưu trữ bọn họ.

Vụ án nguy hiểm

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 của b1sau này. 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 Frankensteinian, được tạo ra từ một số khối b1(những khối Bkế thừa từ A), và một số khối b2(những khối chỉ Bchứa). Ôi chao!

Chuyện gì đã xảy ra? Chà, theo mặc định, C ++ không coi các toán tử gán là virtual. Do đó, dòng a_ref = b1sẽ gọi toán tử gán của A, không phải của B. Điều này là do, đối với các hàm không ảo, kiểu được khai báo (chính thức: tĩnh ) (là A&) xác định hàm nào được gọi, trái ngược với kiểu thực (chính thức: động ) (sẽ là B, vì a_reftham chiếu đến một phiên bản của B) . Bây giờ, Atoán tử gán của 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 những thành viên đó, giữ nguyê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 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. Tuy nhiên, bạn có thể tự cuộn. Bước đầu tiên là làm cho toán tử gán ảo . Điều này sẽ đảm bảo rằng nó luôn là toán tử gán của kiểu thực tế được gọi, không phải của kiểu đã khai báo . Bước thứ hai là sử dụng dynamic_castđể xác minh rằng đối tượng được chỉ định có kiểu tương thích. Bước thứ ba là thực hiện nhiệm vụ thực sự trong một thành viên (được bảo vệ!) assign(), Vì B's assign()có thể sẽ muốn sử dụng Acác thành viên' s assign()để sao chép A'.

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.

158 Black Nov 08 2008 at 18:28

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 bỏ" 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 giống như một A-object (tất cả các hành vi đặc biệt của lớp Bđều bị mất).
42 geh Aug 23 2014 at 01:33

Đâ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'
30 TheArchetypalPaul Nov 08 2008 at 18:14

Kết quả phù hợp thứ ba trên google cho "C ++ cắt" mang lại cho tôi bài viết trên Wikipedia này http://en.wikipedia.org/wiki/Object_slicing và điều này (nóng, nhưng một số bài viết đầu tiên xác định vấn đề): http://bytes.com/forum/thread163565.html

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

Nếu những liên kết đó không cung cấp đủ thông tin cho một "câu trả lời hay", 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 thêm thông tin gì.

29 WalterBright Nov 08 2008 at 18:56

Vấn đề cắt là nghiêm trọng vì nó có thể dẫn đến hỏng bộ nhớ và rất khó để đảm bảo một chương trình không bị như vậy. Để thiết kế nó ra khỏi ngôn ngữ, các lớp hỗ trợ kế thừa chỉ được truy cập bằng 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.

Hãy xem xét lớp A và lớp B bắt nguồn từ A. Sự hỏng bộ nhớ có thể xảy ra nếu phần A có một con trỏ p và một cá thể B trỏ p tới dữ liệu bổ sung của B. Sau đó, khi dữ liệu bổ sung bị loại bỏ, p sẽ trỏ đến rác.

11 KartikMaheshwari Mar 07 2018 at 16:35

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
}

Việc 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 một đối tượng lớp dẫn xuất được cắt ra để tạo thành đối tượng lớp cơ sở.

7 SteveSteiner Nov 09 2008 at 00:38

Vì vậy ... Tại sao mất thông tin có nguồn gốc 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ẽ làm thay đổi giá trị được biểu 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 vào bộ nhớ cache 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 bị cắt ... Nhận một bản sao Tiêu chuẩn mã hóa C ++, 101 hướng dẫn quy tắc và các phương pháp hay nhất. Xử lý 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 hàm tạo bản sao được bảo vệ, một DoClone ảo thuần túy đượ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 triển khai DoClone một cách chính xác. (Phương thức 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 bản sao trên cơ sở rõ ràng cho phép cắt rõ ràng nếu muốn.

7 ididak Nov 09 2008 at 07:31

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ấ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 xung quanh bằng tham chiếu.

Câu trả lời ngắn gọn là bạn cắt đối tượng bằng cách gán đối tượng dẫn xuất cho đố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à một hành vi hợp lý và có 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ểm kỳ quặc / sai lầm của C ++.

6 haberdar Jan 29 2012 at 01:00

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

Nếu D là một 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 kiểu Bắt nguồn cho một biến (hoặc tham số) kiểu Cơ sở.

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ù phép gán ở trên được phép, nhưng giá trị được gán cho biến vật nuôi sẽ làm mất trường giống của nó. Đây được gọi là vấn đề cắt lát .

2. CÁCH KHẮC PHỤC VẤN ĐỀ SLICING

Để giải quyết vấn đề, chúng tôi sử dụng con trỏ đến 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 ptrD (đối tượng lớp con cháu) trỏ tới sẽ bị mất. Ngoài ra, nếu bạn cần sử dụng các hàm thì hàm phải là hàm ảo.

4 Minok Jul 25 2009 at 02:45

Đối với tôi, việc cắt lớp không phải là vấn đề quá lớn ngoài việc các lớp và chương trình của riêng bạn được kiến ​​trúc / thiết kế kém.

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

Đối với tôi, dường như chỉ có kỳ vọng không hợp lý rằng việc cung cấp một lớp con mà một lớp cơ sở được yêu cầu, bằng cách nào đó sẽ dẫn đến kết quả cụ thể của lớp con, sẽ gây ra vấn đề cắt lát. Thiết kế kém của nó trong việc sử dụng phương pháp 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 để có lợi cho hiệu suất hoặc hiệu suất.

3 Dude Oct 18 2012 at 10:22

Được rồi, tôi sẽ thử sau khi đọc nhiều bài đăng giải thích việc cắt đối tượng nhưng không phải cách nó trở nên có vấn đề.

Tình huống xấu có thể dẫn đến hỏng bộ nhớ như sau:

  • Lớp cung cấp (vô tình, có thể do trình biên dịch tạo ra) trên một lớp cơ sở đa hình.
  • Máy khách 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 chức năng thành viên ảo truy cập trạng thái cắt nhỏ.
3 Santosh Mar 13 2014 at 01:08

Cắt nghĩa là dữ liệu được thêm vào bởi một lớp con sẽ 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: Hãy 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ủa baseclass không biết bất cứ điều gì về phần dẫn xuất chỉ có 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 quidkid Nov 29 2012 at 19:32
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; 
}
1 Sorush Sep 12 2020 at 18:27

Tôi thấy tất cả các câu trả lời đề cập đến việc cắt đối tượng xảy ra khi các thành viên dữ liệu được cắt. Ở đây tôi đưa ra một ví dụ rằng các phương thức không bị ghi đè:

class A{
public:
    virtual void Say(){
        std::cout<<"I am A"<<std::endl;
    }
};

class B: public A{
public:
    void Say() override{
        std::cout<<"I am B"<<std::endl;
    }
};

int main(){
   B b;
   A a1;
   A a2=b;

   b.Say(); // I am B
   a1.Say(); // I am A
   a2.Say(); // I am A   why???
}

B (đối tượng b) có nguồn gốc từ A (đối tượng a1 và a2). b và a1, như chúng ta mong đợi, gọi hàm thành viên của chúng. Nhưng từ quan điểm đa hình, chúng ta không mong đợi a2, được gán bởi b, không bị ghi đè. Về cơ bản, a2 chỉ lưu một phần lớp A của b và đó là việc cắt đối tượng trong C ++.

Để giải quyết vấn đề này, nên sử dụng tham chiếu hoặc con trỏ

 A& a2=b;
 a2.Say(); // I am B

hoặc là

A* a2 = &b;
a2->Say(); // I am B

Để biết thêm chi tiết, hãy xem bài đăng của tôi