リトル エンディアン アーキテクチャを検討してください。値は下位バイトが最初に格納されます。そのため、指定された符号なし整数の場合、0 ~ 255 の値が値の最初のバイトに格納されます。任意の値の下位 8 ビットにアクセスするには、そのアドレスへのポインターが必要です。
uint8
したがって、クラスとして実装できます。uint8
のインスタンスが ... 1 バイトであることはわかっています。そこから派生して 、 などを生成する場合uint16
、uint32
インターフェースは抽象化のために同じままですが、最も重要な変更の 1 つは、オブジェクトの具体的なインスタンスのサイズです。
もちろん、 と を実装uint8
しchar
た場合、同様にサイズは同じになる可能性がありますsint8
。
ただし、operator=
とは異なる量のデータを移動しようとしていますuint8
。uint16
ポリモーフィック関数を作成するには、次のいずれかができる必要があります。
a/ データを正しいサイズとレイアウトの新しい場所にコピーすることにより、引数を値で受け取ります。 b/ オブジェクトの場所へのポインタを取得します。 c/ オブジェクト インスタンスへの参照を取得します。
テンプレートを使用して a を実現できるため、多態性はuint128
ポインターや参照なしで機能しますが、テンプレートをカウントしない場合は、実装して関数に渡すとどうなるかを考えてみuint8
ましょう ? 回答: 128 ではなく 8 ビットがコピーされます。
では、ポリモーフィック関数を受け入れるようuint128
にして、それをuint8
. コピーしていたものが残念ながら見つかった場合uint8
、関数は 128 バイトをコピーしようとし、そのうち 127 バイトはアクセス可能なメモリの範囲外でした -> クラッシュします。
次の点を考慮してください。
class A { int x; };
A fn(A a)
{
return a;
}
class B : public A {
uint64_t a, b, c;
B(int x_, uint64_t a_, uint64_t b_, uint64_t c_)
: A(x_), a(a_), b(b_), c(c_) {}
};
B b1 { 10, 1, 2, 3 };
B b2 = fn(b1);
// b2.x == 10, but a, b and c?
編纂された時点fn
では、 の知識はありませんでしB
た。ただし、B
は から派生しているため、ポリモーフィズムによりでA
呼び出すことができるはずです。ただし、返されるオブジェクトは、単一の int で構成される必要があります。fn
B
A
B
この関数にのインスタンスを渡すと、返される{ int x; }
のは a、b、c のない a だけです。
これが「スライス」です。
ポインターと参照を使用しても、これを無料で回避することはできません。検討:
std::vector<A*> vec;
このベクトルの要素は、 へのポインタA
または から派生したものである可能性がありますA
。言語は通常、「vtable」を使用してこれを解決します。これは、オブジェクトのインスタンスへの小さな追加であり、型を識別し、仮想関数の関数ポインターを提供します。次のように考えることができます。
template<class T>
struct PolymorphicObject {
T::vtable* __vtptr;
T __instance;
};
すべてのオブジェクトが独自の vtable を持つのではなく、クラスがそれらを持ち、オブジェクト インスタンスは単に関連する vtable を指します。
問題はスライスではなく、型の正確さです。
struct A { virtual const char* fn() { return "A"; } };
struct B : public A { virtual const char* fn() { return "B"; } };
#include <iostream>
#include <cstring>
int main()
{
A* a = new A();
B* b = new B();
memcpy(a, b, sizeof(A));
std::cout << "sizeof A = " << sizeof(A)
<< " a->fn(): " << a->fn() << '\n';
}
http://ideone.com/G62Cn0
sizeof A = 4 a->fn(): B
私たちがすべきだったのはa->operator=(b)
http://ideone.com/Vym3Lp
ただし、これは A を A にコピーしているため、スライスが発生します。
struct A { int i; A(int i_) : i(i_) {} virtual const char* fn() { return "A"; } };
struct B : public A {
int j;
B(int i_) : A(i_), j(i_ + 10) {}
virtual const char* fn() { return "B"; }
};
#include <iostream>
#include <cstring>
int main()
{
A* a = new A(1);
B* b = new B(2);
*a = *b; // aka a->operator=(static_cast<A*>(*b));
std::cout << "sizeof A = " << sizeof(A)
<< ", a->i = " << a->i << ", a->fn(): " << a->fn() << '\n';
}
http://ideone.com/DHGwun
(i
はコピーされますが、Bj
は失われます)
ここでの結論は、元のインスタンスには、コピーが相互作用する可能性のあるメンバーシップ情報が含まれているため、ポインター/参照が必要であるということです。
しかし、そのポリモーフィズムは C++ 内で完全に解決されているわけではなく、スライスを生成する可能性のあるアクションを提供/ブロックする義務があることを認識しておく必要があります。