누군가가 IRC에서 슬라이싱 문제로 언급했습니다.
누군가가 IRC에서 슬라이싱 문제로 언급했습니다.
답변:
"슬라이스"는 파생 클래스의 객체를 기본 클래스의 인스턴스에 할당하여 정보의 일부를 잃어 버리는 곳입니다. 일부는 "슬라이스"됩니다.
예를 들어
class A {
int foo;
};
class B : public A {
int bar;
};
그래서 형식의 개체는 B
두 개의 데이터 멤버를 가지고 foo
와 bar
.
그런 다음 이것을 작성한다면 :
B b;
A a = b;
그런 다음 b
멤버 정보가에서 bar
손실됩니다 a
.
A a = b;
a
이제 A
복사 유형이 있는 객체 입니다 B::foo
. 나는 그것을 다시 생각하는 것이 실수라고 생각합니다.
B b1; B b2; A& b2_ref = b2; b2 = b1
. 당신은 당신이 복사 한 생각 b1
에 b2
,하지만 당신은하지 않았습니다! 당신은 복사 한 부분 의 b1
에 b2
(의 일부 b1
가 B
상속을 A
), 그리고 다른 부분 왼쪽으로 b2
변경합니다. b2
이제 몇 비트와 몇 개의 b1
덩어리 로 구성된 프랑켄슈타인 생물 입니다 b2
. 어! 답변이 매우 오해의 소지가 있다고 생각하기 때문에 하향 투표.
B b1; B b2; A& b2_ref = b2; b2_ref = b1
" 실제 문제는 당신 이 비가 상 할당 연산자를 가진 클래스에서 파생 된 경우 발생합니다 ." 가요 A
도 유도를위한 것? 가상 기능이 없습니다. 유형에서 파생되는 경우 멤버 함수를 호출 할 수 있다는 사실을 처리해야합니다!
여기의 대부분의 답변은 슬라이싱의 실제 문제가 무엇인지 설명하지 못합니다. 그들은 위험한 조각이 아닌 양성의 경우에 대해서만 설명합니다. 당신이 두 개의 클래스를 처리하고 있다는, 다른 답변처럼 가정 A
하고 B
, B
도출 (공개)에서 A
.
이 상황에서, C ++은 인스턴스 통과 할 수 있습니다 B
에 A
의 할당 연산자 (그리고 복사 생성자를). 이는의 인스턴스를 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_cast
assign()
B
assign()
A
assign()
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
}
};
순전히 편의를 위해 의 인스턴스는의 인스턴스를 반환한다는 것을 알기 때문에 B
의 operator=
공변량은 반환 유형을 재정의합니다 .B
derived
값이 기대 코드에 부여 될 수있다 base
값, 또는 임의의 유도 된 기준을 기초 기준으로서 이용 될 수있다이. 두 개념을 개별적으로 다루는 유형 시스템이있는 언어를보고 싶습니다. 파생 참조가 기본 참조로 대체 될 수있는 경우가 많지만 파생 인스턴스는 기본 참조로 대체 할 수 없어야합니다. 인스턴스를 변환 할 수 있어야하지만 참조를 대체하지 않아야하는 경우도 많이 있습니다.
기본 클래스 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
이 손실 됨).wantAnA
(그 이름이 의미 하듯이!)를 원하는 A
,의 그것은 도착 후 어떤 것을. 그리고 A
의지 의 인스턴스는 어 A
.. 어떻게 놀랍습니까?
derived
가 type으로 수행하는 자동 캐스팅에 A
있습니다. 암시 적 캐스팅은 항상 C ++에서 예기치 않은 동작의 원인입니다. 캐스팅이 발생한 코드를 로컬에서 확인하는 것은 종종 어렵 기 때문입니다.
이것들은 모두 좋은 대답입니다. 값 대 참조로 객체를 전달할 때 실행 예제를 추가하고 싶습니다.
#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'
"C ++ 자르는"에 대한 구글의 세 번째 경기는 나에게이 위키 백과 문서 제공 http://en.wikipedia.org/wiki/Object_slicing을 그리고 이것은 (가열하지만, 처음 몇 게시물은 문제를 정의) : http://bytes.com/ forum / thread163565.html
하위 클래스의 객체를 수퍼 클래스에 할당 할 때입니다. 수퍼 클래스는 서브 클래스의 추가 정보를 전혀 모르고이를 저장할 공간이 없으므로 추가 정보가 "슬라이스 오프"됩니다.
해당 링크에 "좋은 답변"에 대한 정보가 충분하지 않은 경우 질문을 편집하여 더 많은 정보를 찾으십시오.
슬라이싱 문제는 메모리 손상을 초래할 수 있기 때문에 심각하며 프로그램으로 인해 문제가 발생하지 않도록 보장하기가 매우 어렵습니다. 언어를 사용하여 언어를 디자인하려면 상속을 지원하는 클래스는 참조가 아닌 값으로 액세스 할 수 있어야합니다. D 프로그래밍 언어에는이 속성이 있습니다.
클래스 A와 클래스 B를 고려하십시오. A 파트에 포인터 p가 있고 p가 B의 추가 데이터를 가리키는 B 인스턴스가 있으면 메모리 손상이 발생할 수 있습니다. 그런 다음 추가 데이터가 분리되면 p는 가비지를 가리 킵니다.
Derived
암시 적으로으로 변환 할 수 Base
있습니다.) 이것은 공개 폐쇄 원칙과 반비례하고 유지 관리 부담이 커집니다.
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
}
파생 된 클래스 개체가 기본 클래스 개체에 할당되면 파생 클래스 개체의 추가 특성이 분리되어 기본 클래스 개체를 형성 할 때 개체 슬라이싱이 발생합니다.
C ++의 슬라이싱 문제는 객체의 값 의미론에서 발생하며 주로 C 구조체와의 호환성으로 인해 유지됩니다. 객체를 수행하는 대부분의 다른 언어에서 발견되는 "정상적인"객체 동작을 달성하려면 명시 적 참조 또는 포인터 구문을 사용해야합니다. 즉, 객체는 항상 참조로 전달됩니다.
짧은 대답은 파생 객체를 기본 객체 에 value 로 할당하여 객체를 슬라이스한다는 것입니다 . 즉, 나머지 객체는 파생 객체의 일부일뿐입니다. 가치 의미론을 유지하기 위해 슬라이싱은 합리적인 동작이며 상대적으로 드물게 사용되며 대부분의 다른 언어에는 존재하지 않습니다. 어떤 사람들은이 기능을 C ++의 기능이라고 생각하지만, 많은 사람들은이를 C ++의 단점 중 하나라고 생각했습니다.
struct
.
Base
정확히 sizeof(Base)
바이트를 가져와야 할 수 있으므로 "할당"(on-stack-copy) )는 파생 클래스 멤버를 복사하지 않으며 오프셋이 sizeof를 벗어났습니다. 포인터 메모리 장소와 크기가 고정되어 있기 때문에 스택이 매우 volitile 반면, 다른 사람처럼 바로 사용 포인터, "데이터 손실"피하려면
그렇다면 ... 파생 정보를 잃어버린 이유는 무엇입니까? ... 파생 클래스의 작성자가 추가 정보를 자르면 객체가 나타내는 값이 변경되도록 표현을 변경했을 수 있습니다. 파생 클래스가 특정 작업에 더 효율적이지만 기본 표현으로 다시 변환하는 데 비용이 많이 드는 표현을 캐시하는 데 사용되는 경우 발생할 수 있습니다.
또한 누군가가 슬라이싱을 피하기 위해해야 할 일을 언급해야한다고 생각했습니다. C ++ 코딩 표준, 101 가지 규칙 지침 및 모범 사례를 받으십시오. 슬라이싱 처리는 # 54입니다.
문제를 완전히 처리하기 위해 다소 복잡한 패턴을 제안합니다. 보호 된 복사 생성자, 보호 된 순수 가상 DoClone 및 퍼스트 파생 클래스가 DoClone을 올바르게 구현하지 못한 경우 알려주는 어설 션이있는 퍼블릭 클론이 있습니다. (복제 방법은 다형성 객체의 적절한 딥 카피를 만듭니다.)
원하는 경우 명시 적 슬라이싱을 허용하는 기본 명시 적에 복사 생성자를 표시 할 수도 있습니다.
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 (하위 클래스 객체)가 가리키는 동적 변수의 데이터 멤버 또는 멤버 함수는 손실되지 않습니다. 또한 함수를 사용해야하는 경우 함수는 가상 함수 여야합니다.
dog
클래스 Pet
( breed
데이터 멤버) 의 일부가 아닌 일부 상태가 변수에 복사되지 않는 문제는 pet
무엇입니까? 코드는 Pet
데이터 멤버 에만 관심이 있습니다. 원하지 않는 경우 슬라이싱은 확실히 "문제"이지만 여기서는 볼 수 없습니다.
((Dog *)ptrP)
"내가 사용하는 것이 좋습니다static_cast<Dog*>(ptrP)
Dog::breed
) SLICING과 관련된 오류가 아니라는 것을 이해하고 있습니까?
나 자신의 수업과 프로그램이 제대로 설계 / 설계되지 않은 경우를 제외하고는 슬라이싱은 큰 문제가되지 않습니다.
수퍼 클래스 유형의 매개 변수를 사용하는 메소드에 서브 클래스 오브젝트를 매개 변수로 전달하는 경우, 반드시 알고 있어야하며 내부적으로 알아야합니다. 호출 된 메소드는 수퍼 클래스 (일명베이스 클래스) 오브젝트에서만 작동합니다.
베이스 클래스가 요청되는 서브 클래스를 제공하면 어떻게 든 서브 클래스 특정 결과가 발생하고 슬라이싱이 문제가 될 것이라는 부당한 기대 만 보입니다. 메소드를 사용하는 디자인이 좋지 않거나 서브 클래스 구현이 좋지 않습니다. 나는 일반적으로 편의성 또는 성능 향상을 위해 좋은 OOP 디자인을 희생 한 결과를 추측합니다.
슬라이스는 서브 클래스의 오브젝트가 값에 의해 또는 기본 클래스 오브젝트를 예상하는 함수에서 전달되거나 리턴 될 때 서브 클래스에 의해 추가 된 데이터가 삭제됨을 의미합니다.
설명 : 다음 클래스 선언을 고려하십시오.
class baseclass
{
...
baseclass & operator =(const baseclass&);
baseclass(const baseclass&);
}
void function( )
{
baseclass obj1=m;
obj1=m;
}
기본 클래스 복사 함수는 파생에 대한 정보를 알지 못하므로 파생의 기본 부분 만 복사됩니다. 이것을 일반적으로 슬라이싱이라고합니다.
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;
}
파생 클래스 개체가 기본 클래스 개체에 할당되면 파생 클래스 개체의 모든 멤버가 기본 클래스에없는 멤버를 제외한 기본 클래스 개체에 복사됩니다. 이 멤버들은 컴파일러에 의해 분리됩니다. 이것을 개체 슬라이싱이라고합니다.
다음은 예입니다.
#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'
방금 슬라이싱 문제를 겪고 바로 여기에 도착했습니다. 여기에 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의.