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

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

ただいまの
回答率

89.87%

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

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 1,038

strike1217

score 579

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

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

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


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

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

class Base {}
class Derived : public Base {}

Derived obj;
Base *ptr = &obj;


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

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

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

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

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


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

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

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

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

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

typedef struct __node {
    int data;
    struct *__node next;
}Node;

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

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

struct Base {
    int member = 3489;

    void function(){
        std::cout << "Base::funtion" << std::endl;
    }

    virtual void func(){
        std::cout << "Base::virtual_func" << std::endl;
    }
};

class Derived final : public Base {
    public:
        int Derived_member = 3829;

        void function(){
            std::cout << "Derived::function" << std::endl;
        }

        void func(){
            std::cout << "Derived::virtual_func" << std::endl;
        }
};


Base base_obj;
Derived *derived_obj_ptr = dynamic_cast<Derived *>(&base_obj);


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

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

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

class Base { ... };
class Derived : public Base { ... };

Base obj;
Derived obj2;

obj2 = obj;  // ok!
obj = obj2   // ERROR!!


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

環境は、Linux g++です。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+2

こんにちは。

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

「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型のオブジェクトが入っていることは、実行しなくてもわかりますよね??

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

bool make_derived;
std::cin >> make_derived;
Base* 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つ目にダウンキャストはなぜ危険なのでしょうか??

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

#include <iostream>
struct Base
{
    int x;
};

struct Derived : public Base
{
    int y;
};

int main()
{
    Base b;
    b.x=0;
    Derived* d_ptr = static_cast<Derived*>(&b);
    std::cout << d_ptr->y;
}

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/28 15:38

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

    キャンセル

  • 2018/08/28 15:42

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

    ダウンキャストには成功してますよ。ただし、危険なダウンキャストであることをコンパイラが検出できるようなコードだったので警告してくれたというもののようです。clangでコンパイルすると警告でませんでした。https://wandbox.org/permlink/NYzfVXfmsixq8Efv

    キャンセル

  • 2018/08/28 17:26

    > ダウンキャストには成功してますよ。
    ああ。そうですね。言葉が不適切でした。

    仮想関数テーブルをポイントしているポインタを参照することで、動的型チェックが行えるわけですね。

    キャンセル

+2

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/28 01:35

    > これは配列各要素のサイズが一定である性質とメモリの連続性を利用するものです。
    ああ!この2つの性質が隠れていることに気づきませんでした。><

    > 巡回ポインタの型が実体と一致しないケースを考えなければなりません
    その通りですね!! C言語では、型の違うポインタを代入することなんて殆ど無いですね。
    C++になると、クラスの派生の部分で登場するので、一気に分かりにくくなりますね。

    キャンセル

+1

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

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はなにもいいません。

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

#include <iostream>
struct Base {
    int member = 3489;

    void function(){
        std::cout << "Base::funtion" << std::endl;
    }

    virtual void func(){
        std::cout << "Base::virtual_func" << std::endl;
    }
};

class Derived final : public Base {
    public:
        int Derived_member = 3829;

        void function(){
            std::cout << "Derived::function" << std::endl;
        }

        void func(){
            std::cout << "Derived::virtual_func" << std::endl;
        }
};


Base base_obj;
Base* b_p1 = &base_obj;
Derived derived_obj;
Base* b_p2 = &derived_obj;
Derived *derived_obj_ptr1 = dynamic_cast<Derived *>(&base_obj);
Derived *derived_obj_ptr2 = dynamic_cast<Derived *>(b_p1);
Derived *derived_obj_ptr3 = dynamic_cast<Derived *>(new Base());
Derived *derived_obj_ptr4 = dynamic_cast<Derived *>(b_p2);
int main(){
    std::cout
        << reinterpret_cast<void*>(&base_obj) << std::endl
        << reinterpret_cast<void*>(derived_obj_ptr1) << std::endl
        << reinterpret_cast<void*>(derived_obj_ptr2) << std::endl
        << reinterpret_cast<void*>(derived_obj_ptr3) << std::endl
        << reinterpret_cast<void*>(derived_obj_ptr4) << std::endl;
}

https://wandbox.org/permlink/SGYDJwnQ5vfoOxSO

実行結果は

0x6012c0
0
0
0
0x6012e0

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

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

ref:

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/28 12:59

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

    キャンセル

  • 2018/08/28 13:22

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

    キャンセル

  • 2018/08/28 13:29

    https://theolizer.com/cpp-school1/cpp-school1-33/

    こちらを参考にさせてもらいました。

    キャンセル

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

  • ただいまの回答率 89.87%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる