質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

3回答

812閲覧

C++のポインタについて

strike1217

総合スコア651

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

1クリップ

投稿2018/08/27 15:47

ポインタについての質問があります。

1つ目に、型が違うポインタは、どう解釈されるんでしょうか??

C++

1char qtr = 3289; 2int * ptr = &qtr;

こんなコードがあったとします。
RISC系CPUならアライメントにまたがるため例外が送出されると思います。通常このような使い方はしないですよね。

int型のポインタにchar型のアドレスを代入しています。
この場合、ptrは、**int型のポインタと解釈されるのでしょうか?**それとも、char型のポインタでしょうか?

C++

1class Base {} 2class Derived : public Base {} 3 4Derived obj; 5Base *ptr = &obj;

こちらは、どうでしょうか?よく見かけるコードです。ptrは、結局のところどちらのポインタだと解釈されるんでしょうか??

2つ目
dynamic_castがわかりません。
私の持っている本に以下のような事が書いてあります。

ポインターやリファレンスの指し示しているオブジェクトの本当のクラス型は、実行時にしか分からない。

え?なんで??
上記のコードの例だと、ptrに、Derived型のオブジェクトが入っていることは、実行しなくてもわかりますよね??

C++

1void f(Base & base){ 2 Derived & d = static_cast<Derived &>(base); // baseがderivedを参照しているかどうかは、わからない。 3}

関数内では、確かに、参照しているかどうかは不明です。
ですが・・・f関数を呼び出した時に渡すオブジェクトは、実行時でなくてもわかりますよね?

ポインターやリファレンスの指し示しているオブジェクトの本当のクラス型は、実行時にしか分からない。そのため、常に変換に失敗する可能性がある。そのため、dynamic_castを使う場合は、常に変換が失敗するかもしれないという前提のもとにコードを書かななければならない。

ん??一体なんのことだかサッパリ。

3つ目にダウンキャストはなぜ危険なのでしょうか??
基本型のポインターを派生型のポインターに変換する

線形リストなどを作る時に、自己参照型のポインタって良く見ますよね。

C++

1typedef struct __node { 2 int data; 3 struct *__node next; 4}Node;

これは、ポインタが不完全型であっても使用可能だからですよね。
この理由は、ポインタは、アドレスを代入するものであって、構造体の中身がどうなっていようが、アドレスの値に影響しないからだと考えました。(間違っているかもしれません。)

この理屈で考えてみます。テスト用のコードを作りました。適当です。
以下のコードは、コンパイルは通ります。しかし、コンパイルする際にNever Succeedと出てきます。
なぜでしょうか??

C++

1struct Base { 2 int member = 3489; 3 4 void function(){ 5 std::cout << "Base::funtion" << std::endl; 6 } 7 8 virtual void func(){ 9 std::cout << "Base::virtual_func" << std::endl; 10 } 11}; 12 13class Derived final : public Base { 14 public: 15 int Derived_member = 3829; 16 17 void function(){ 18 std::cout << "Derived::function" << std::endl; 19 } 20 21 void func(){ 22 std::cout << "Derived::virtual_func" << std::endl; 23 } 24}; 25 26 27Base base_obj; 28Derived *derived_obj_ptr = dynamic_cast<Derived *>(&base_obj);

以下のコードを見てみます。
基本クラスのオブジェクトを派生クラスのオブジェクトに代入することは可能です。
しかし、逆に、派生クラスのオブジェクトを基本クラスのオブジェクトに代入することはできません。

これは、派生クラスには、基本クラスにないメンバ変数を持っている可能性があるから、コピーのしようがない・・・ということでしょう。

(この時に使われるのは・・・コピー代入演算子ですかね?基本クラスのオブジェクトが右辺に来ている・・・時は、どうなんでしょうか?)

C++

1class Base { ... }; 2class Derived : public Base { ... }; 3 4Base obj; 5Derived obj2; 6 7obj2 = obj; // ok! 8obj = obj2 // ERROR!!

オブジェクトの方は分かるのですが、ポインタの方はなぜできないのかわかりません。
また、ダウンキャストができる条件を教えてください。

環境は、Linux g++です。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

こんにちは。

こんなコードがあったとします。

「int型のポインタにchar型のポインタは代入できないエラー」になる筈です。

この場合、ptrは、int型のポインタと解釈されるのでしょうか?それとも、char型のポインタでしょうか?

int * ptr = reinterpret_cast<int*>(&qtr);とすれば代入できます。
つまり、ptrはint型へのポインタなのです。

こちらは、どうでしょうか?よく見かけるコードです。ptrは、結局のところどちらのポインタだと解釈されるんでしょうか??

こちらはちょっと悩ましいですよね。C++は普通に記述す限り、型に厳密です。Base *ptr = &obj;のptrはBase型へのポインタとして宣言していますので、何を代入しようがBase型へのポインタです。
なのである意味安心して使えます。

ただし、指している先はDerived型のオブジェクトです。指している先を仮にBase型として取り扱っても未定義動作が発生する危険はありません。

上記のコードの例だと、ptrに、Derived型のオブジェクトが入っていることは、実行しなくてもわかりますよね??

確かにそのコードであれば分かりますが、それは「特別なケース」なのです。分かる場合もありますが、一般的には分からないと考えるのが正しいです。

C++

1bool make_derived; 2std::cin >> make_derived; 3Base* ptr=(make_derived)?new Derived:new Base;

のような場合、実行時にしか分かりません。

また、例示されている関数 f() をfoo.cppで定義し、bar.cppから呼び出したとします。
foo.cppをコンパイルしている時のコンパイラは、bar.cppの情報を全く持っていませんので、コンパイル時にはどんなパラメータで呼び出されているか分かりません。
foo.cppが何かのライブラリとして配布されていて、それを入手した人がbar.cppから呼び出すような場合にありがちな話です。

ご提示されているコードは、Base型オブジェクトが渡されると危険なコードです。
そのような時は dynamic_cast<>を使い、結果がnullptrになっていないことを確認することで安全になります。(なお、nullポインタを*で参照へ変換すると未定義動作を引き起こしますので、ポインタで処理する必要があります。)

dynamic_cast<>は意外なほど遅いので使わないに越したことはないですが、使わないで処理するのは結構面倒なので、速度を要求されない場所なら使うのも有りと思います。(ダウンキャストそのものを否定する人もいますが、使った方が良い時も結構あります。全ての派生クラス用の仮想関数を定義するとか、できれば避けたいものです。)

3つ目にダウンキャストはなぜ危険なのでしょうか??

以下のソースを実行すると何が表示されるでしょうか?
つまり未定義動作を引き起こしますので、危険なのです。

C++

1#include <iostream> 2struct Base 3{ 4 int x; 5}; 6 7struct Derived : public Base 8{ 9 int y; 10}; 11 12int main() 13{ 14 Base b; 15 b.x=0; 16 Derived* d_ptr = static_cast<Derived*>(&b); 17 std::cout << d_ptr->y; 18}

基本クラスのオブジェクトを派生クラスのオブジェクトに代入することは可能です。
しかし、逆に、派生クラスのオブジェクトを基本クラスのオブジェクトに代入することはできません。
これは、派生クラスには、基本クラスにないメンバ変数を持っている可能性があるから、コピーのしようがない・・・ということでしょう。

えっ! 完全に逆ですよ。
派生クラスは基底クラスへ代入できます。そして、スライシングが発生して泣くことがあります。
しかし、逆はコンパイラが禁止しています。派生クラスにだけある変数に値を設定できないからだと思います。
なお、基底クラスへのconst参照を取る代入演算子やコンストラクタを定義すれば通ります。定義した人の責任で設定すれば良いからです。

投稿2018/08/27 18:00

編集2018/08/27 18:03
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2018/08/28 02:02

なるほど! ポインタの方は理解しました。 ポインタは、基本的に何が入っているかは実行時にしか分からないと考えてしまって良いんですね。 dynamic_castの実行時型チェックというのは、不正なポインタが入っていた時に、自動的にnullptrを代入してくれるんですね。 > えっ! 完全に逆ですよ。派生クラスは基底クラスへ代入できます。 あ、そうでしたっけ。
yumetodo

2018/08/28 02:05

書こうとしていたっことをだいたい言ってくれた感ある
strike1217

2018/08/28 02:10

dynamic_castの説明の箇所に以下のような事が書かれています。 動的な型チェックを使うために、dynamic_castのオペランドのクラスはポリモーフィック型でなければならない。つまり少なくとも1つのvirtual関数を持たななければならない。 仮想関数がなければ、動的な型チェックが発動されない・・・ということですよね。 なんででしょうか?? 仮想関数の本質がよくわからない。
yumetodo

2018/08/28 02:13

正確にはvirtual基底クラスでもいいんじゃないのか・・・?
strike1217

2018/08/28 02:19

あああ。2回に分けて質問しようと思っていたのですが、もう1つの方の質問に話が食い込んで言ってしまってます。 virtual関数については再度質問いたします。 > 以下のソースを実行すると何が表示されるでしょうか? 不定な数値が表示されますね。 > つまり未定義動作を引き起こしますので、危険なのです。 はぁぁぁ!なるほど。 質問中に載せたコードは、何がおかしいのでしょうか? Never Succedd というエラーが表示されるやつです。ダウンキャストに成功しないのはなぜでしょう?
strike1217

2018/08/28 02:26

> 正確にはvirtual基底クラスでもいいんじゃないのか・・・? virtual基底クラス・・・virtual関数を1つでも持っているクラスが基本クラスになっているクラスのことですか? なら、そうですね。
strike1217

2018/08/28 02:40

static_castでもダウンキャストできるんですね。てっきりdynamic_castでしかできないものだと思っていました。 動的な型チェックが発動しないdynamic_castは、static_castと等価である。と考えて良いんでしょうか?
strike1217

2018/08/28 04:23

動的な型チェックが発動しないdynamic_cast そんなものありませんね。 virtual関数がない場合は単純にコンパイルエラーでした。
strike1217

2018/08/28 04:27

とりあえず、解決いたしました。 BAに迷いましたが、選ばせてもらいます。 virtual関数については再度質問いたします。
Chironian

2018/08/28 06:38

> 仮想関数がなければ、動的な型チェックが発動されない・・・ということですよね。 > なんででしょうか?? 仮想関数があると仮想関数テーブル・ポインタが陰のメンバ変数に追加されます。 その値はコンストラクト時にコンストラクトされたクラスの仮想関数テーブルを指すようにポイントされます。 その値をみれば、動的チェックできると理解しています。 (しかし、実際にはdynamic_castはもっともっと複雑なことをやっているらしいです。そのこころは私は把握できたいないです。)
Chironian

2018/08/28 06:42

> 質問中に載せたコードは、何がおかしいのでしょうか? Never Succedd というエラーが表示されるやつです。ダウンキャストに成功しないのはなぜでしょう? ダウンキャストには成功してますよ。ただし、危険なダウンキャストであることをコンパイラが検出できるようなコードだったので警告してくれたというもののようです。clangでコンパイルすると警告でませんでした。https://wandbox.org/permlink/NYzfVXfmsixq8Efv
strike1217

2018/08/28 08:26

> ダウンキャストには成功してますよ。 ああ。そうですね。言葉が不適切でした。 仮想関数テーブルをポイントしているポインタを参照することで、動的型チェックが行えるわけですね。
guest

0

直接の回答ではありません、一般論です。

Cの中のポインタの扱いとC++の中での扱いを混同するのは危険です。

例えば、配列要素を巡回する上でポインタを使うことがあると思います。Cの場合、これは配列各要素のサイズが一定である性質とメモリの連続性を利用するものです。

が、C++の場合は、継承機能があります。従って、巡回ポインタの型が実体と一致しないケースを考えなければなりません。この場合はCの考え方が通用しません。ポインタ変数のインクリメントでメモリ中途番地を指すことになるからです。

この場合、具体的にどのように対応しているかまでは存じませんが、考え方に違いがあることに気がついていただければ幸いです。

投稿2018/08/27 16:29

HogeAnimalLover

総合スコア4830

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2018/08/27 16:35

> これは配列各要素のサイズが一定である性質とメモリの連続性を利用するものです。 ああ!この2つの性質が隠れていることに気づきませんでした。>< > 巡回ポインタの型が実体と一致しないケースを考えなければなりません その通りですね!! C言語では、型の違うポインタを代入することなんて殆ど無いですね。 C++になると、クラスの派生の部分で登場するので、一気に分かりにくくなりますね。
guest

0

寝て起きてからちゃんと回答しますが、取り急ぎ。

strike1217さんはおそらく型システムの話と実行時の話の区別がついていないように思われます。継承絡みのことはHogeAnimalLoverさん指摘の通り。


virtual基底クラス・・・virtual関数を1つでも持っているクラスが基本クラスになっているクラスのことですか?

違います。

質問中に載せたコードは、何がおかしいのでしょうか? Never Succedd というエラーが表示されるやつです。ダウンキャストに成功しないのはなぜでしょう?

https://wandbox.org/permlink/B8AO4UuUfybl3HoO

prog.cc:29:61: warning: dynamic_cast of 'Base base_obj' to 'class Derived*' can never succeed Derived *derived_obj_ptr = dynamic_cast<Derived *>(&base_obj); ^

これですかね?まずこれclangはなにもいいません。

で、ちょっとコードをいじってみましょう。

cpp

1#include <iostream> 2struct Base { 3 int member = 3489; 4 5 void function(){ 6 std::cout << "Base::funtion" << std::endl; 7 } 8 9 virtual void func(){ 10 std::cout << "Base::virtual_func" << std::endl; 11 } 12}; 13 14class Derived final : public Base { 15 public: 16 int Derived_member = 3829; 17 18 void function(){ 19 std::cout << "Derived::function" << std::endl; 20 } 21 22 void func(){ 23 std::cout << "Derived::virtual_func" << std::endl; 24 } 25}; 26 27 28Base base_obj; 29Base* b_p1 = &base_obj; 30Derived derived_obj; 31Base* b_p2 = &derived_obj; 32Derived *derived_obj_ptr1 = dynamic_cast<Derived *>(&base_obj); 33Derived *derived_obj_ptr2 = dynamic_cast<Derived *>(b_p1); 34Derived *derived_obj_ptr3 = dynamic_cast<Derived *>(new Base()); 35Derived *derived_obj_ptr4 = dynamic_cast<Derived *>(b_p2); 36int main(){ 37 std::cout 38 << reinterpret_cast<void*>(&base_obj) << std::endl 39 << reinterpret_cast<void*>(derived_obj_ptr1) << std::endl 40 << reinterpret_cast<void*>(derived_obj_ptr2) << std::endl 41 << reinterpret_cast<void*>(derived_obj_ptr3) << std::endl 42 << reinterpret_cast<void*>(derived_obj_ptr4) << std::endl; 43}

https://wandbox.org/permlink/SGYDJwnQ5vfoOxSO

実行結果は

0x6012c0 0 0 0 0x6012e0

のようになりました。まあ当たり前ですよね。

静的にあきらかに失敗するとわかるdynamic_castの使用にはエラーや警告を出してくれるようです。

ref:

投稿2018/08/27 17:14

編集2018/08/28 03:06
yumetodo

総合スコア5850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2018/08/27 17:16

> 型システムの話と実行時の話の区別がついていない むむ!そうですか! わかりました。とりあえず、おやすみなさいです。
strike1217

2018/08/28 03:25

ん??? Derived *derived_obj_ptr1 = dynamic_cast<Derived *>(&base_obj); なぜここが0と表示されるのでしょうか? nullptrということですよね。 つまり、dynamic_castに失敗している・・・ということですよね。 なぜ失敗するのでしょうか?? 上記のコードでダウンキャストに失敗する理由がわからないのです。 virtualを消して、static_castでダウンキャストしました。そうするとキャストできます。 なんで?? (すいません。質問中のコードは記述を省略しています。 Base base_obj; Derived *derived_obj_ptr = dynamic_cast<Derived *>(&base_obj); が、書かれているのは、main()内です。)
yumetodo

2018/08/28 03:40

えっ、逆になんで失敗しないと・・・? Derived *derived_obj_ptr4 = dynamic_cast<Derived *>(b_p2); が成功している理由を考えれば自明でしょう
strike1217

2018/08/28 03:45

ん?あ!もしかしてdynamic_castでダウンキャストってできないんですか??
strike1217

2018/08/28 03:49

いや・・・そんなことは無いですよね? dynamic_castでダウンキャストってできますよね。
yumetodo

2018/08/28 03:50

ダウンキャストが安全に行えるのには条件があります。
strike1217

2018/08/28 03:55

あ!「安全に」というのが抜け落ちていました。 安全でないダウンキャスト → dynamic_cast never succeed となる。 安全なダウンキャスト → ok!! static_castの場合、安全なダウンキャストでなくてもダウンキャスト可 ということですか! その条件とは、なんでしょう?? 上記のコードだとどこが安全になってないのでしょうか?
yumetodo

2018/08/28 03:59

ぐぐろう。すぐでてくる。
strike1217

2018/08/28 04:22

ああ!理解しました。 okです!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問