객체 슬라이싱이란 무엇입니까?


답변:


608

"슬라이스"는 파생 클래스의 객체를 기본 클래스의 인스턴스에 할당하여 정보의 일부를 잃어 버리는 곳입니다. 일부는 "슬라이스"됩니다.

예를 들어

class A {
   int foo;
};

class B : public A {
   int bar;
};

그래서 형식의 개체는 B두 개의 데이터 멤버를 가지고 foobar.

그런 다음 이것을 작성한다면 :

B b;

A a = b;

그런 다음 b멤버 정보가에서 bar손실됩니다 a.


66
매우 유익하지만 메소드 호출 중에 슬라이싱이 발생하는 방법에 대한 예는 stackoverflow.com/questions/274626#274636 을 참조하십시오 (일반 할당 예제보다 위험을 약간 강조합니다).
블레어 콘래드

55
흥미 롭군 나는 C ++에서 15 년 동안 프로그래밍 해 왔으며 효율성과 개인 스타일의 문제로 객체를 항상 참조로 전달했기 때문에이 문제는 결코 발생하지 않았습니다. 좋은 습관이 어떻게 당신을 도울 수 있는지 보여줍니다.
Karl Bielefeldt

10
@Felix 고맙지 만 (포인터 산술이 아니기 때문에) 캐스트가 작동한다고 생각하지 않습니다. A a = b; a이제 A복사 유형이 있는 객체 입니다 B::foo. 나는 그것을 다시 생각하는 것이 실수라고 생각합니다.

37
이것은 "슬라이스"또는 최소한 양성 변형이 아닙니다. 당신이 할 경우 실제 문제가 발생합니다 B b1; B b2; A& b2_ref = b2; b2 = b1. 당신은 당신이 복사 한 생각 b1b2,하지만 당신은하지 않았습니다! 당신은 복사 한 부분b1b2(의 일부 b1B상속을 A), 그리고 다른 부분 왼쪽으로 b2변경합니다. b2이제 몇 비트와 몇 개의 b1덩어리 로 구성된 프랑켄슈타인 생물 입니다 b2. 어! 답변이 매우 오해의 소지가 있다고 생각하기 때문에 하향 투표.
fgp

24
@fgp 주석은 B b1; B b2; A& b2_ref = b2; b2_ref = b1" 실제 문제는 당신 이 비가 상 할당 연산자를 가진 클래스에서 파생 된 경우 발생합니다 ." 가요 A도 유도를위한 것? 가상 기능이 없습니다. 유형에서 파생되는 경우 멤버 함수를 호출 할 수 있다는 사실을 처리해야합니다!
curiousguy

509

여기의 대부분의 답변은 슬라이싱의 실제 문제가 무엇인지 설명하지 못합니다. 그들은 위험한 조각이 아닌 양성의 경우에 대해서만 설명합니다. 당신이 두 개의 클래스를 처리하고 있다는, 다른 답변처럼 가정 A하고 B, B도출 (공개)에서 A.

이 상황에서, C ++은 인스턴스 통과 할 수 있습니다 BA의 할당 연산자 (그리고 복사 생성자를). 이는의 인스턴스를 B로 변환 할 수 있기 때문에 const A&할당 연산자와 복사 생성자가 인수를 기대합니다.

양성 사건

B b;
A a = b;

아무 일도 일어나지 않습니다. 인스턴스 A가의 사본 인 경우 요청한 것이 B바로 그 것입니다. 물론 님의 a일부 b회원 은 포함되지 않지만 어떻게해야합니까? 그것은이있어 A, 결국이 아닌 B, 그래서도하지 않은 들어 이 회원들에 대해, 저장할 수있을 것입니다 혼자하자.

위험한 사건

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

나중에 b2사본이 될 것이라고 생각할 수도 있습니다 b1. 그러나 아아, 그렇지 않습니다 ! 그것을 조사하면, b2일부 덩어리 b1(에서 B상속 된 덩어리 A)와 일부 덩어리 b2(포함 된 덩어리 ) 로 만들어진 프랑켄슈타인 생물 임을 알 수 B있습니다. 아야!

어떻게 된 거예요? 기본적으로 C ++은 할당 연산자를로 취급하지 않습니다 virtual. 따라서 라인 a_ref = b1은의 할당 연산자가 A아닌 의 할당 연산자를 호출합니다 B. 이 아닌 가상 함수의 경우가 있기 때문이다 선언 (정식 정적 형 (인) A&)을 반대로, 호출 된 함수를 판정 실제 (정식 : 동적 ) 유형 (것이다 B이후 a_ref의 레퍼런스 인스턴스 B) . 지금 A의 할당 연산자는 분명 구성원의 선언 단지에 대해 알고 A는 추가 회원두고 만 복사합니다 있도록 B변경합니다.

해결책

일반적으로 객체의 일부에만 할당하는 것은 의미가 없지만 불행히도 C ++은이를 금지하는 기본 제공 방법을 제공하지 않습니다. 그러나 자신의 롤을 할 수 있습니다. 첫 번째 단계는 할당 연산자를 가상으로 만드는 것 입니다. 이렇게하면 선언 된 유형이 아니라 호출되는 실제 유형의 할당 연산자 가 항상 보장됩니다 . 두 번째 단계는 할당 된 객체가 호환 가능한 유형인지 확인하는 데 사용 됩니다. 세 번째 단계는 (보호!) 멤버의 실제 할당을하는 것입니다 때문에, 의는 아마도 사용하는 것이 좋습니다 의 복사 ,의 회원.dynamic_castassign()Bassign()Aassign()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
  }
};

순전히 편의를 위해 의 인스턴스는의 인스턴스를 반환한다는 것을 알기 때문에 Boperator=공변량은 반환 유형을 재정의합니다 .B


11
어느 하나를 이럴 문제가 상속 내포 될 수 대체 가능성의 두 가지 종류가 있다는 것이다 derived값이 기대 코드에 부여 될 수있다 base값, 또는 임의의 유도 된 기준을 기초 기준으로서 이용 될 수있다이. 두 개념을 개별적으로 다루는 유형 시스템이있는 언어를보고 싶습니다. 파생 참조가 기본 참조로 대체 될 수있는 경우가 많지만 파생 인스턴스는 기본 참조로 대체 할 수 없어야합니다. 인스턴스를 변환 할 수 있어야하지만 참조를 대체하지 않아야하는 경우도 많이 있습니다.
supercat

16
나는 당신의 "복잡한"사건에서 그렇게 나쁜 것이 무엇인지 이해하지 못합니다. 1) 클래스 A의 오브젝트에 대한 참조를 가져오고 2) 오브젝트 b1을 클래스 A에 캐스트하고 그 내용을 클래스 A의 참조에 복사하십시오. 실제로 잘못된 것은 뒤에 올바른 논리입니다. 주어진 코드. 다시 말해, 작은 이미지 프레임 (A)을 가져와 더 큰 이미지 (B) 위에 놓은 다음 나중에 더 큰 이미지가보기 흉하게 보인다고 불평하면서 그 프레임을 페인트했습니다. 화가가 원했던 것처럼 꽤 좋아 보입니다. :)
Mladen B.

12
문제는 달리 C ++은 기본적으로 매우 강력한 종류의 대체 가능성을 가정한다는 것입니다. 하위 클래스 인스턴스에서 기본 클래스의 작업이 올바르게 작동해야합니다. 그리고 컴파일러가 할당과 같이 자동 생성 한 작업의 경우에도 마찬가지입니다. 따라서 이와 관련하여 자신의 작업을 망칠 수는 충분하지 않으며 컴파일러에서 생성 된 잘못된 작업을 명시 적으로 비활성화해야합니다. 또는 물론, 일반적으로 좋은 제안 anway ;-) 공개 상속, 멀리
FGP

14
또 다른 일반적인 방법은 단순히 복사 및 할당 연산자를 비활성화하는 것입니다. 상속 계층 구조 내 클래스의 경우 일반적으로 참조 또는 포인터 대신 값을 사용해야 할 이유가 없습니다.
Siyuan Ren

13
뭐야? 운영자가 가상으로 표시 될 수있을 줄은
몰랐습니다.

153

기본 클래스 A와 파생 클래스 B가있는 경우 다음을 수행 할 수 있습니다.

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

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

이제 메소드 wantAnA는의 사본이 필요합니다 derived. 그러나 derived클래스 B가 기본 클래스에없는 추가 멤버 변수를 발명 할 수 있으므로 오브젝트 를 완전히 복사 할 수 없습니다 A.

따라서을 호출하기 wantAnA위해 컴파일러는 파생 클래스의 모든 추가 멤버를 "슬라이스 오프"합니다. 결과는 생성하지 않으려는 객체 일 수 있습니다.

  • 불완전 할 수 있습니다.
  • A-object 처럼 동작합니다 (클래스의 모든 특수 동작 B이 손실 됨).

40
C ++은 자바 가 아니다 ! 경우 wantAnA(그 이름이 의미 하듯이!)를 원하는 A,의 그것은 도착 후 어떤 것을. 그리고 A의지 의 인스턴스는 어 A.. 어떻게 놀랍습니까?
fgp

82
@fgp : 함수에 A전달하지 않기 때문에 놀랍습니다 .
Black

10
@fgp : 동작이 비슷합니다. 그러나 일반적인 C ++ 프로그래머에게는 덜 명확 할 수 있습니다. 내가 그 질문을 이해하는 한, 아무도 "불평"하지 않습니다. 컴파일러가 상황을 처리하는 방법에 관한 것입니다. Imho, (const) 참조를 전달하여 슬라이싱을 피하는 것이 좋습니다.
Black

8
@ThomasW 아니요, 상속을 버리지 않고 참조를 사용합니다. wantAnA의 서명이 void wantAnA (const A & myA) 인 경우 슬라이싱이 없었습니다. 대신, 호출자의 오브젝트에 대한 읽기 전용 참조가 전달됩니다.
Black

14
문제는 대부분 컴파일러 derived가 type으로 수행하는 자동 캐스팅에 A있습니다. 암시 적 캐스팅은 항상 C ++에서 예기치 않은 동작의 원인입니다. 캐스팅이 발생한 코드를 로컬에서 확인하는 것은 종종 어렵 기 때문입니다.
pqnet

41

이것들은 모두 좋은 대답입니다. 값 대 참조로 객체를 전달할 때 실행 예제를 추가하고 싶습니다.

#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());
}

출력은 다음과 같습니다.

Call by reference
I am a 'B'

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

안녕하세요. 좋은 답변이지만 한 가지 질문이 있습니다. 내가 이와 같은 것을하면 ** dev d; base * b = & d; ** 슬라이싱도 발생합니까?
Adrian

@Adrian 파생 클래스에 새로운 멤버 함수 나 멤버 변수를 도입하면 기본 클래스 포인터에서 직접 액세스 할 수 없습니다. 그러나 여전히 오버로드 된 기본 클래스 가상 함수 내에서 액세스 할 수 있습니다. 이것을보십시오 : godbolt.org/z/LABx33
Vishal Sharma

30

"C ++ 자르는"에 대한 구글의 세 번째 경기는 나에게이 위키 백과 문서 제공 http://en.wikipedia.org/wiki/Object_slicing을 그리고 이것은 (가열하지만, 처음 몇 게시물은 문제를 정의) : http://bytes.com/ forum / thread163565.html

하위 클래스의 객체를 수퍼 클래스에 할당 할 때입니다. 수퍼 클래스는 서브 클래스의 추가 정보를 전혀 모르고이를 저장할 공간이 없으므로 추가 정보가 "슬라이스 오프"됩니다.

해당 링크에 "좋은 답변"에 대한 정보가 충분하지 않은 경우 질문을 편집하여 더 많은 정보를 찾으십시오.


29

슬라이싱 문제는 메모리 손상을 초래할 수 있기 때문에 심각하며 프로그램으로 인해 문제가 발생하지 않도록 보장하기가 매우 어렵습니다. 언어를 사용하여 언어를 디자인하려면 상속을 지원하는 클래스는 참조가 아닌 값으로 액세스 할 수 있어야합니다. D 프로그래밍 언어에는이 속성이 있습니다.

클래스 A와 클래스 B를 고려하십시오. A 파트에 포인터 p가 있고 p가 B의 추가 데이터를 가리키는 B 인스턴스가 있으면 메모리 손상이 발생할 수 있습니다. 그런 다음 추가 데이터가 분리되면 p는 가비지를 가리 킵니다.


3
메모리 손상이 발생할 수있는 방법을 설명하십시오.
foraidt

4
사본 ctor가 실수로 vptr을 재설정한다는 것을 잊었습니다. 그러나 A에 포인터가 있으면 B가 여전히 손상을 입을 수 있으며, B는 슬라이스 된 B 섹션을 가리 키도록 설정합니다.
Walter Bright

18
이 문제는 슬라이싱에만 국한되지 않습니다. 포인터를 포함하는 모든 클래스는 기본 할당 연산자 및 복사 생성자를 사용하여 모호한 동작을합니다.
Weeble

2
@Weeble-이러한 경우 기본 소멸자, 할당 연산자 및 복사 생성자를 무시합니다.
Bjarke Freund-Hansen

7
@Weeble : 일반 포인터 수정보다 객체 슬라이싱을 악화시키는 것은 슬라이싱이 발생하지 않도록 확실히하기 위해 기본 클래스는 파생 된 모든 클래스에 대해 변환 생성자 제공해야한다는 것 입니다. (왜? 빠진 파생 클래스는 기본 클래스의 복사 관리자가 선택할 수 있기 때문에 Derived암시 적으로으로 변환 할 수 Base있습니다.) 이것은 공개 폐쇄 원칙과 반비례하고 유지 관리 부담이 커집니다.
j_random_hacker

10

C ++에서는 파생 클래스 개체를 기본 클래스 개체에 할당 할 수 있지만 다른 방법으로는 불가능합니다.

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
}

파생 된 클래스 개체가 기본 클래스 개체에 할당되면 파생 클래스 개체의 추가 특성이 분리되어 기본 클래스 개체를 형성 할 때 개체 슬라이싱이 발생합니다.


8

C ++의 슬라이싱 문제는 객체의 값 의미론에서 발생하며 주로 C 구조체와의 호환성으로 인해 유지됩니다. 객체를 수행하는 대부분의 다른 언어에서 발견되는 "정상적인"객체 동작을 달성하려면 명시 적 참조 또는 포인터 구문을 사용해야합니다. 즉, 객체는 항상 참조로 전달됩니다.

짧은 대답은 파생 객체를 기본 객체 에 value 로 할당하여 객체를 슬라이스한다는 것입니다 . 즉, 나머지 객체는 파생 객체의 일부일뿐입니다. 가치 의미론을 유지하기 위해 슬라이싱은 합리적인 동작이며 상대적으로 드물게 사용되며 대부분의 다른 언어에는 존재하지 않습니다. 어떤 사람들은이 기능을 C ++의 기능이라고 생각하지만, 많은 사람들은이를 C ++의 단점 중 하나라고 생각했습니다.


5
" 정상적인 객체 행동 "은 "정상적인 객체 행동"이 아니며, 참조 시맨틱 입니다. 그리고 그것은 임의의 OOP 성직자가 당신에게 말한 C , 호환성 또는 다른 말도 안됩니다struct .
curiousguy

4
@curiousguy 아멘 형제. 가치 의미론이 C ++를 너무나 강력하게 만드는 것들 중 하나 인 경우 C ++이 Java가 아닌 상황에서 얼마나 자주 소멸되는지는 슬픈 일입니다.
fgp

이것은 기발한 기능이 아닙니다. arg 또는 (같은) 유형의 스택 변수를 할당하는 함수로 함수를 호출하면 가능한 정렬을 사용하여 메모리에서 Base정확히 sizeof(Base)바이트를 가져와야 할 수 있으므로 "할당"(on-stack-copy) )는 파생 클래스 멤버를 복사하지 않으며 오프셋이 sizeof를 벗어났습니다. 포인터 메모리 장소와 크기가 고정되어 있기 때문에 스택이 매우 volitile 반면, 다른 사람처럼 바로 사용 포인터, "데이터 손실"피하려면
크롤

C ++의 잘못된 기능입니다. 파생 개체를 기본 개체에 할당하는 것은 금지되어야하며 파생 개체를 참조 또는 기본 클래스의 포인터에 바인딩하는 것은 괜찮습니다.
John Z. Li

7

그렇다면 ... 파생 정보를 잃어버린 이유는 무엇입니까? ... 파생 클래스의 작성자가 추가 정보를 자르면 객체가 나타내는 값이 변경되도록 표현을 변경했을 수 있습니다. 파생 클래스가 특정 작업에 더 효율적이지만 기본 표현으로 다시 변환하는 데 비용이 많이 드는 표현을 캐시하는 데 사용되는 경우 발생할 수 있습니다.

또한 누군가가 슬라이싱을 피하기 위해해야 ​​할 일을 언급해야한다고 생각했습니다. C ++ 코딩 표준, 101 가지 규칙 지침 및 모범 사례를 받으십시오. 슬라이싱 처리는 # 54입니다.

문제를 완전히 처리하기 위해 다소 복잡한 패턴을 제안합니다. 보호 된 복사 생성자, 보호 된 순수 가상 DoClone 및 퍼스트 파생 클래스가 DoClone을 올바르게 구현하지 못한 경우 알려주는 어설 션이있는 퍼블릭 클론이 있습니다. (복제 방법은 다형성 객체의 적절한 딥 카피를 만듭니다.)

원하는 경우 명시 적 슬라이싱을 허용하는 기본 명시 적에 복사 생성자를 표시 할 수도 있습니다.


3
" 당신은 또한 전혀 도움 이되지 않는 기본 명시 " 에 복사 생성자를 표시 할 수 있습니다 .
curiousguy

6

1. 슬라이싱 문제의 정의

D가 기본 클래스 B의 파생 클래스 인 경우 파생 유형의 개체를 Base 유형의 변수 (또는 매개 변수)에 할당 할 수 있습니다.

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

위의 할당이 허용되지만 변수 pet에 할당 된 값은 해당 유형 필드를 잃습니다. 이것을 슬라이싱 문제 라고합니다 .

2. 슬라이싱 문제를 해결하는 방법

문제를 해결하기 위해 동적 변수에 대한 포인터를 사용합니다.

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

이 경우, ptrD (하위 클래스 객체)가 가리키는 동적 변수의 데이터 멤버 또는 멤버 함수는 손실되지 않습니다. 또한 함수를 사용해야하는 경우 함수는 가상 함수 여야합니다.


7
"슬라이스"부분은 이해하지만 "문제"는 이해하지 못합니다. dog클래스 Pet( breed데이터 멤버) 의 일부가 아닌 일부 상태가 변수에 복사되지 않는 문제는 pet무엇입니까? 코드는 Pet데이터 멤버 에만 관심이 있습니다. 원하지 않는 경우 슬라이싱은 확실히 "문제"이지만 여기서는 볼 수 없습니다.
curiousguy

4
" ((Dog *)ptrP)"내가 사용하는 것이 좋습니다static_cast<Dog*>(ptrP)
curiousguy

'ptrP'를 통해 삭제할 때 가상 소멸자 ( 'string'의 소멸자가 호출되지 않음)없이 문자열 'breed'가 결국 메모리 누수를 만들 것이라고 지적하는 것이 좋습니다 ... 왜 문제가 있습니까? 수정은 대부분 적절한 클래스 디자인입니다. 이 경우의 문제점은 상속 할 때 가시성을 제어하기 위해 생성자를 작성하는 것이 지루하고 쉽게 잊혀진다는 것입니다. 다형성이 없거나 언급되지 않았기 때문에 코드가있는 위험 영역 근처에 아무것도 가지지 않습니다 (슬라이스는 객체를 자르지 만 여기에서 프로그램이 충돌하지는 않습니다).
Dude

24
-1 실제 문제를 완전히 설명하지 못합니다. C ++에는 Java와 같은 참조 의미론이 아닌 가치 의미론 이 있으므로 이것이 전부 예상됩니다. "수정"은 실제로 끔찍한 C ++ 코드 의 예입니다 . 동적 할당에 의지하여 이러한 유형의 슬라이싱과 같은 존재하지 않는 문제를 "고정"하는 것은 버그가있는 코드, 메모리 누수 및 끔찍한 성능을위한 레시피입니다. 이하는 것으로 되어 슬라이스가 나쁜 경우가 있지만,이 응답도 실패한 그들을 지적. 힌트 : 참조를 통해 할당하면 문제가 시작됩니다 .
fgp

정의되지 않은 유형의 멤버에 액세스하려고 시도하는 것이 ( Dog::breed) SLICING과 관련된 오류가 아니라는 것을 이해하고 있습니까?
Croll

4

나 자신의 수업과 프로그램이 제대로 설계 / 설계되지 않은 경우를 제외하고는 슬라이싱은 큰 문제가되지 않습니다.

수퍼 클래스 유형의 매개 변수를 사용하는 메소드에 서브 클래스 오브젝트를 매개 변수로 전달하는 경우, 반드시 알고 있어야하며 내부적으로 알아야합니다. 호출 된 메소드는 수퍼 클래스 (일명베이스 클래스) 오브젝트에서만 작동합니다.

베이스 클래스가 요청되는 서브 클래스를 제공하면 어떻게 든 서브 클래스 특정 결과가 발생하고 슬라이싱이 문제가 될 것이라는 부당한 기대 만 보입니다. 메소드를 사용하는 디자인이 좋지 않거나 서브 클래스 구현이 좋지 않습니다. 나는 일반적으로 편의성 또는 성능 향상을 위해 좋은 OOP 디자인을 희생 한 결과를 추측합니다.


3
그러나 Minok, 그 객체에 대한 참조를 전달하지 않는다는 것을 기억하십시오. 해당 객체의 새 복사본을 전달하지만 기본 클래스를 사용하여 프로세스에서 복사합니다.
Arafangion

기본 클래스의 보호 된 복사 / 할당 및이 문제가 해결되었습니다.
Dude

1
네가 옳아. 모범 사례는 추상 기본 클래스를 사용하거나 복사 / 할당에 대한 액세스를 제한하는 것입니다. 그러나 일단 발견되면 알아 채기가 쉽지 않으며 돌보는 것을 잊기 쉽습니다. 슬라이스 *로 가상 메소드를 호출하면 액세스 위반없이 도망 가면 신비한 일이 발생할 수 있습니다.
Dude

1
대학의 C ++ 프로그래밍 과정에서 우리가 만든 모든 클래스에 대해 기본 생성자, 복사 생성자 및 할당 연산자 및 소멸자를 작성 해야하는 모범 사례가 있음을 상기합니다. 이런 식으로 수업을 쓰는 동안 사본 구성 등이 필요한 방식으로 발생했는지 확인했습니다.
Minok

3

좋아, 객체 슬라이싱을 설명하는 많은 게시물을 읽은 후에 시도해 보지만 문제가되는 방법은 아닙니다.

메모리 손상을 초래할 수있는 악의적 인 시나리오는 다음과 같습니다.

  • 클래스는 다형성 기본 클래스에 실수로 컴파일러가 생성 한 할당을 제공합니다.
  • 클라이언트는 파생 클래스의 인스턴스를 복사하고 슬라이스합니다.
  • 클라이언트는 분리 된 상태에 액세스하는 가상 멤버 함수를 호출합니다.

3

슬라이스는 서브 클래스의 오브젝트가 값에 의해 또는 기본 클래스 오브젝트를 예상하는 함수에서 전달되거나 리턴 될 때 서브 클래스에 의해 추가 된 데이터가 삭제됨을 의미합니다.

설명 : 다음 클래스 선언을 고려하십시오.

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

기본 클래스 복사 함수는 파생에 대한 정보를 알지 못하므로 파생의 기본 부분 만 복사됩니다. 이것을 일반적으로 슬라이싱이라고합니다.


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
추가 세부 정보를 주시겠습니까? 답변이 이미 게시 된 답변과 어떻게 다릅니 까?
Alexis Pigeon

2
더 많은 설명이 나쁘지 않을 것 같아요.
looper

-1

파생 클래스 객체가 기본 클래스 객체에 할당되면 파생 클래스 객체의 추가 속성이 기본 클래스 객체에서 분리 (삭제)됩니다.

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

파생 클래스 개체가 기본 클래스 개체에 할당되면 파생 클래스 개체의 모든 멤버가 기본 클래스에없는 멤버를 제외한 기본 클래스 개체에 복사됩니다. 이 멤버들은 컴파일러에 의해 분리됩니다. 이것을 개체 슬라이싱이라고합니다.

다음은 예입니다.

#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;
}

다음을 생성합니다.

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

좋은 예가 아니기 때문에 하향 조정되었습니다. d를 b로 복사하는 대신 d와 e가 여전히 존재하지만 Base에 해당 멤버가없는 포인터를 사용하면 작동하지 않습니다. 귀하의 예는 수업에없는 회원에게 액세스 할 수 없음을 보여줍니다.
Stefan Fabian

-2

방금 슬라이싱 문제를 겪고 바로 여기에 도착했습니다. 여기에 2 센트를 추가하겠습니다.

"프로덕션 코드"(또는 비슷한 것)의 예를 보자.


액션을 전달하는 무언가가 있다고 가정 해 봅시다. 예를 들어 제어 센터 UI.
이 UI에는 현재 발송할 수있는 항목의 목록이 필요합니다. 따라서 디스패치 정보가 포함 된 클래스를 정의합니다. 그것을 호출하자 Action. 따라서 Action일부 멤버 변수가 있습니다. 간단하게하기 위해 a std::string name와 a 인 2 만 있습니다 std::function<void()> f. 그런 다음 멤버를 void activate()실행하는 f입니다.

따라서 UI가 std::vector<Action>제공됩니다. 다음과 같은 기능을 상상해보십시오.

void push_back(Action toAdd);

이제 UI의 관점에서 어떻게 보이는지 설정했습니다. 지금까지 아무런 문제가 없습니다. 그러나이 프로젝트에서 일하는 다른 사람은 갑자기 더 많은 정보를 필요로하는 특별한 행동이 있다고 결정 Action합니다. 어떤 이유로. 람다 캡처로 해결할 수도 있습니다. 이 예제는 코드에서 1-1이 아닙니다.

그래서 그 사람은 Action자신의 맛을 더하기 위해 파생됩니다 .
그는 집에서 만든 수업의 사례를 학교로 전달 push_back했지만 프로그램은 건초로 연결됩니다.

무슨 일이야?
당신 이 짐작할 있듯이 : 객체가 슬라이스되었습니다.

인스턴스의 추가 정보가 손실 f되어 정의되지 않은 동작이 발생하기 쉽습니다.


나는이 예제에 대해 이야기 할 때 정말 일을 상상할 수없는 사람들에 대한 빛을 가져다 희망 A의와 B어떤 방식으로 파생되는 s의.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.