topazの遊び場

いろいろやってみる

そろそろC++やるぞパート17 ポインター ~ スマートポインタ/shared_ptr ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は、Microsoftドキュメントと他の記事を参考にして、スマートポインタの一つshared_ptrについて行います。

shared_ptrの宣言

make_shared< > ヘルパー関数を使用して、shared_ptrの宣言ができます。

shared_ptr<{型}> {変数名} = make_shared<{型}>(<{初期値}>);

int型やstring型の場合はこのように書くことができます。

shared_ptr<int> sp = make_shared<int>(10);
shared_ptr<int> sp = make_shared<int>();
shared_ptr<string> sp2 = make_shared<string>("Hello World!");

また、調べた限りでは、shared_ptrの宣言位は型推論auto)を用いているところが多いようです。

auto sp = make_shared<int>(124);
auto sp2 = make_shared<string>("Hello World!");

初回化は、このような初期化方法もあります。

shared_ptr<int> sp3(new int(246));

ただしこれは、公式のところに少し効率が悪いと書いてあります。

// Ok, but slightly less efficient.
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.

そのため、これからmake_sharedを使うことにします。

shared_ptrとは

前回unique_ptrとの違いとして、所有権を複数に共有することができます

unique_ptrの場合
宣言した変数を他の変数に代入することができませんでした。

auto up = make_unique<int>(235);
auto up2 = up;  // コンパイルエラー

shared_ptrの場合
宣言した変数を他の変数に代入することができます。

auto sp = make_shared<int>(145);
auto sp2 = sp;  // コンパイル/実行可能

仕組み

shared_ptrは複数の所有権を持つ変数を作ることができるため、所有権を持つ変数を数える参照カウントが存在します。コピーした時に、参照カウントがインクリメントされ、ブロックを外れたり、明示的解放が行われた時に、デクリメントされます。この参照カウントが0になった時に、メモリの解放が行われます。

参考リスト

感想

今回はshared_ptrについて触れて行きましたが、これが一番変数と同じように使用できるスマートポインタという感じでした。ただ、循環参照という、テーマが一つ残っているのですが、これは次回内容もカラムので次回に行います。

次回は、次のweak_ptrを触ってみたいと思います。

そろそろC++やるぞパート16 ポインター ~ スマートポインタ/unique_ptr ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は、Microsoftドキュメントと他の記事を参考にして、スマートポインタの一つunique_ptrについて行います。

unique_ptr 宣言

make_unique< > ヘルパー関数を使用して、unique_ptrの宣言ができます。

unique_ptr<{型}> {変数名} = make_unique<{型}>(<{初期値}>);

int型やstring型の場合はこのように書くことができます。

unique_ptr<int> intSmtPtr = make_unique<int>(2345);
unique_ptr<int> intSmtPtr2 = make_unique<int>();
unique_ptr<string> m = make_unique<string>("Hello World!");

また、調べた限りでは、unique_ptrの宣言位は型推論auto)を用いているところが多いようです。

auto intSmtPtr = make_unique<int>(2345);
auto m = make_unique<string>("Hello World!");

型推論の詳細はまた後日行いたいと思います。

また、unique_ptrの宣言はmake_uniqueヘルパー関数を使わずに、このように書くこともできます。(参考1参考2

unique_ptr<int> intSmtPtr(new int(2345));
unique_ptr<int> intSmtPtr2(new int());
int *x = new int(2453);
unique_ptr<int> intSmtPtr3(x);

ですが、C++14 以降ではmake_uniqueを使うことが推奨されているらしいです。。(はっきり書かれている場所を見つけられませんでした。)
理由の一つとして、一つの生ポインターを二つのunique_ptrに所有権を渡すことができてしまうためです。

int *x = new int(2453);
unique_ptr<int> intSmtPtr3(x);
unique_ptr<int> intSmtPtr4(x);

この結果、intSmtPtr4(x);の宣言は実行できるのですが、スコープからintSmtPtr3intSmtPtr4が共にdeleteされることになった際に、複数回 delete することになり、実行時エラーが生じてしまいます。

自分もmake_uniqueなどのヘルパー関数を使用する方針にしようと思います。


余談 ----------

余談ですが、「make_unique;提案new_upとしたい」 という記事も見つけて面白いなと思いました。(考え方の一つですが)

簡単にいうと、new(メモリの割り当てを行う)の行為を行なっているのに、make_uniqueにはnewというキーワードが入っていないため変更したいというものでした。

こちらのブログのコードをお借りして、このコードを使用します。

//----------------------------------------
// uniq_ptrの補助
template <typename T, typename... Args>
unique_ptr<T> new_up(Args &&...args)
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template <typename T>
using up = unique_ptr<T>;
//----------------------------------------

すると、unique_ptrをこのように宣言することができました。

// 変更前
unique_ptr<int> intSmtPtr = make_unique<int>(2345);
auto intSmtPtr2 = make_unique<int>(2345);

// 変更後
up<int> intSmtPtr3 = new_up<int>(2345);

こんなことも出来るのかと勉強になりました。

余談終了----------


unique_ptrとは

宣言することは出来ましたが、「これは何?」という話です。

unique_ptrはスマートポインタの中でも、ポインターを共有できないスマートポインタです。

つまり、コピーができません。

生ポインタや他のスマートポインタではコピーができますが、unique_ptrはコピーすることができません。

int *intPtr = new int(1234);
// Ok
int *intPtr2 = intPtr;

unique_ptr<int> intSmtPtr = make_unique<int>(2345);
// コンパイルエラー
unique_ptr<int> intSmtPtr2 = intSmtPtr;

参考リスト

感想

今回は、スマートポインタの一つunique_ptrを見てみました。まだ一つ目なので、他との比較ができていませんが、「素直なポインタ」という印象です。

次回は、次のshared_ptrを触ってみたいと思います。

そろそろC++やるぞパート15 ポインター ~ スマートポインタ ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は、スマートポインタをわかりやすい記事を参考にして概要を掴みます。

スマートポインタとは

簡単にいうと、生ポインタより使いやすくなったポインタです。


生ポインタの問題点

1. newで生成した後に、deleteしないとメモリリークが生じる。

int main()
{
    int *intPtr = new int();
    *intPtr = 1234;
    cout << *intPtr << endl;
    // deleteしていない
    return 0;
}

2. 念の為と、何度もdeleteするとエラーが生じる。

int main()
{
    int *intPtr = new int();
    *intPtr = 1234;
    cout << *intPtr << endl;
    delete intPtr;
    delete intPtr;  // 2回目のdeleteは実行時エラーとなる
    return 0;
}

この生ポインタのバグの元になりやすいメモリの確保/解放周りを楽にすることができるのが、スマートポインタです。

コード上では、このようになります。

int main()
{
    shared_ptr<int> x = make_shared<int>();
    *x = 1234;
    cout << *x << std::endl;
    return 0;
}

スマートポインタのアドレスの確認

unique_ptr<>を使用してアドレスの確認を行います。通常のポインタとスマートポインタの変数自体のアドレスと中身を見てみます。

int *intPtr = new int(7654);
unique_ptr<int> l(intPtr);

cout << &intPtr << endl;
cout << intPtr << endl;
cout << &l << endl;
cout << l << endl;

Output:

0x16d21ada8
0x142f04090
0x16d21ada0
0x142f04090

スタック領域に intポインタ intPtrと uniqueポインタlのメモリが割り振られ、両ポインタが同じアドレス(0x142f04090)のヒープ領域を参照しています。

所有権

スマートポインタはリソース(メモリなど)への所有権という考え方で出来ています。

  • スマートポインタは初期化後の生ポインタを所有
  • スマートポインタを介してメモリにアクセス
  • 生ポインタから解放されることはない

スマートポインタを定義した後に、元の生ポインタを削除しようとすると実行時エラーが生じます。

int *intPtr = new int(7654);
unique_ptr<int> l(intPtr);
delete intPtr;

C++ 標準ライブラリのスマートポインタ

  • unique_ptr
  • shared_ptr
  • weak_ptr

参考記事

感想

スマートポインタ楽しみにして心構えていましたが、実際はそこまで難しくもなさそうで、とても便利!という感じでした。

スマートポインタの種類によって所有権の取り扱いが変わるようなので、次回からは一つずつ見ていきます。

そろそろC++やるぞパート14 ポインター ~ ポインターの初期化とメンバーアクセス「->」と「.」の違い ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

ポインターの初期化

初期化にはnewを使用します。ヒープ配列の場合はこのように書きます。

int *ar = new int[5]{3, 5, 6};

また、MyClassという自作クラスの場合は、new 型名()と書きます。

MyClass *mc = new MyClass();

この宣言で初期化した場合はヒープ領域にメモリが確保されるため、deleteでメモリの解放を行う必要があります。

delete[] ar;
delete(mc);

ポインターメンバーアクセス演算子

演算子ドキュメントを参考にします。
->演算子は、ポインターの間接参照と、メンバーアクセスが組み合わされます。pubicな変数Xと関数Printを持ったMyClassクラスについて考えます。

class MyClass
{
public:
    int X;
    void Print()
    {
        cout << "Hello" << endl;
    }
};

->演算子を使用して、このようにアクセスすることができます。

MyClass *mc = new MyClass();
mc->X = 124;
int x = mc->X;

また、ポインターの間接参照とメンバーアクセスを明示的に書くこともでき、以下の二つは同じことを表している。

int x = mc->X;
int x = (*mc).X;

今回扱っているクラスは、ポインターとして扱う場合ですが、以前触れたクラスでは、クラスを変数として扱っていました。

MyClass mcClass = MyClass();
mcClass.X = 13;

メンバー変数を->演算子でアクセスするか、.演算子でアクセスするかはこの違いです。

void*ポインター

voidへのポインターは、単に生のメモリの場所を指します。
(実際は違うかもしれませんが、自分のイメージは全てのポインター型の基本型です。)

様々な型のポインターvoid *に代入することができます。

int *intPtr = new int();
float *floatPtr = new float();
MyClass *mcPtr = new MyClass();
void *voidPtr = intPtr;
voidPtr = floatPtr;
voidPtr = mcPtr;

C#で基底クラスのobjectに変換することがないように、C++void *ポインターもあまり使うことはないように思います。

関数へのポインター

自分の認識では、C#でいう delegateActionのようなものです。

コードを見た方がわかりやすいので、先にコードを見ます。

void PrintHello(string name)
{
    cout << "Hello " << name << endl;
}

void PrintWorld(string name)
{
    cout << "World " << name << endl;
}

class MyClass
{
public:
    int X;
    void (*PrintX)(string);
};

int main()
{
    MyClass *mc = new MyClass();
    mc->PrintX = PrintHello;

    mc->PrintX("topaz");

    mc->PrintX = PrintWorld;

    mc->PrintX("zzz");

    return 0;
}
// Output:
// Hello topaz
// World zzz

MyClassのpublicに定義されている、void (*PrintX)(string);が関数ポインターです。stringを受け取りvoidを返すPrintXを定義しています。
関数ポインターへの代入(割り当て)は=で行います。

mc->PrintX = PrintHello;
mc->PrintX = PrintWorld;

呼び出しは、通常の関数と同じように( )で行います。

mc->PrintX("topaz");
mc->PrintX("zzz");

関数ポインターへ代入を行う前に実行すると、segmentation faultが起こるので注意が必要です。

コンストラクタでの代入も行えます。(クラスのコンストラクタ

class MyClass
{
public:
    MyClass(void (*f)(string)) : PrintX(f)
    {
    }
    void (*PrintX)(string);
};

参考記事

感想

クラスの要素へのアクセス方法で->.の違いが分からなかったのですが、->は間接参照とメンバーアクセスが組み合わされていると知れたのがよかったです。
また、C#Actionなどを多用するので関数へのポインターも必要機能だなと思いました。

次回は、いろいろなところで進められているスマートポインターについて行おうと思います。

そろそろC++やるぞパート13 ポインター ~ ポインターとは ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

ポインター

ポインターはメモリのアドレスを格納する変数です。

int a = 123;
int *intPtr = &a:

この場合intPtrの変数がポインターです。

以下がCとC++の主要な3つの使用目的です。

  1. ヒープ上に新しいオブジェクトを割り当てる。
  2. 関数を他の関数に渡す。
  3. 配列またはその他のデータ構造内の要素を反復処理する。

1は以前に登場したnewでの割り当てです。

int *intPtr;
intPtr = new int;

C言語では、生ポインターが使われており、エラーの原因となっており、C++では非推奨です。
最新の C++ では、

  • オブジェクトの割り当てにはスマート ポインター
  • データ構造の走査には反復子
  • 関数を渡すにはラムダ式

が使用されます。
これらについてこれから数回かけて触れています。

ポインター

ポインターは、スマート ポインターなど、カプセル化オブジェクトによって有効期間が制御されないポインターのことです。(スマートポインターについてはまた今度)
ポインターには、別の非ポインター変数のアドレスや、値nullPtrを割り当てることができます。

公式のコード

    int* intPtr = nullptr; // declare pointer and initialize it
                      // so that it doesn't store a random address
    int i = 5;
    intPtr = &i; // assign pointer to address of object
    int j = *intPtr; // dereference p to retrieve the value at its address

int*型(int pointer 型)変数intPtrnullptrを代入することができます。

ポインターは型指定されたオブジェクトまたはvoidを指す必要があります。

型名*   ポインター;
void*    ポインター

ヒープ領域にオブジェクトを割り当てると、そのアドレスをポインター形式で受け取ります。このようなポインター所有ポインターと呼びます。

class MyClass
{
public:
    void Print()
    {
        cout << "Hello" << endl;
    }
};

MyClass型のオブジェクトをnewで初期化することができます。この時に受け取ったmcが所有ポインターです。

MyClass *mc = new MyClass();
mc->Print();
delete (mc);

newで初期化したオブジェクトはヒープ領域に割り当てられるため、使い終わった時にdeleteして、メモリを解放する必要があります。メモリの解放を行わないと、メモリリークが発生します。

ポインター算術演算

ポインターはインクリメントやデクリメントをすることができ、別のアドレスを参照することができます。この操作をポインター算術演算と呼びます。
これは配列や他のデータ構造で使われます。

参考記事

感想

スタックやヒープの配列の回にしっかりメモリのアドレスまで見ていたおかげで、ポインターは入りやすいなという印象でした。また、スマートポインターが何者か気になるところです。

次回は、生ポインターの初期化から行います。

そろそろC++やるぞパート12 ポインター ~ 導入 ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

今回からポインターに入ります。難しいという噂を聞いたことがあるので、ドキュメントに入る前に簡単に説明してくれている記事を参考に概要を掴みます。

ポインター

値は変数に。 変数の値はメモリに。
メモリの位置はアドレスと呼び。
アドレスはポインター変数が持ちます。

上図のように中身(値)がアドレスになっている変数を、ポインター変数ポインターと呼びます。

配列の回などにも何回か出てきています。

宣言

間接参照演算子*)をつけることでポインターとして宣言できます。*の位置は変数の前でも型の前でもどちらでも構いません、

int *intPtr:
int* intPtr:

関節参照

関節参照演算子*)をつけると、ポインターが持つアドレスの中身にアクセスすることができます。

int main()
{
    int v = 1256;
    int *intPtr = &v;
    cout << &v << endl;
    cout << &intPtr << endl;
    cout << intPtr << endl;
    cout << *intPtr << endl;
    return 0;
}
// Output:
// 0x16b74adc8
// 0x16b74adc0
// 0x16b74adc8
// 1256

ここが少し厄介なポイントなのですが、

int *intPtr = &v;

と宣言をしていますが、

*intPtr == &v    (値 と ポインターの比較でエラー
intPtr == &v        (ポインター同士の比較で、True

比較するのはintPtr&vになります。このように考えると

int* intPtr = &v

と定義する方がわかりやすいのかなと思います。

動的割り当て

ポインターnewを使用して動的に割り当てることができます。

int *intPtr;
intPtr = new int;

宣言時に*intPtrのメモリの確保は行います。ただ、intPtrの中身のアドレスには不定の値が入っています。
new intと動的に割り当てることで、別の場所にint分のメモリを確保してそのアドレスをintPtrの値に代入します。

int main()
{
    int *intPtr;
    cout << &intPtr << endl;
    cout << intPtr << endl;
    intPtr = new int;
    cout << &intPtr << endl;
    cout << intPtr << endl;
    cout << *intPtr << endl;
    return 0;
}
// Output:
// 0x16f112dc0
// 0x100cefcbc
// 0x16f112dc0
// 0x158e068e0
// 0

割り当て前と割り当て後でintPtrの出力が変わっていることがわかります。

    cout << intPtr << endl;

他には配列のポインターや関数渡しなど、すでに触れているものばかりでした。

参考リンク

感想

ポインターが難しいという印象でしたが、ざっと見た感じそこまで難しくないかなという印象でした。

次回からは、ドキュメントを読んでしっかり見ていきたいと思います。

そろそろC++やるぞパート0 目次

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

目次

  1. そろそろC++やるぞパート0 目次 - topazの遊び場
  2. そろそろC++をやるパート1 - topazの遊び場
  3. そろそろC++やるぞパート2 - topazの遊び場
  4. そろそろC++やるぞパート3 関数 - topazの遊び場
  5. そろそろC++やるぞパート4 クラス入門 - topazの遊び場
  6. そろそろC++やるぞパート5 クラス入門2 - topazの遊び場
  7. そろそろC++やるぞパート6 クラス入門3 - topazの遊び場
  8. そろそろC++やるぞパート7 配列 ~ 導入と定数式 ~ - topazの遊び場
  9. そろそろC++やるぞパート8 配列2 ~ スタックとヒープ ~ - topazの遊び場
  10. そろそろC++やるぞパート9 配列3 ~ 初期化と関数渡し ~ - topazの遊び場
  11. そろそろC++やるぞパート10 配列4 ~ 多次元配列 ~ - topazの遊び場
  12. そろそろC++やるぞパート11 配列4 ~ 配列の要素へのアクセスと添字演算子 ~ - topazの遊び場
  13. そろそろC++やるぞパート12 ポインター ~ 導入 ~ - topazの遊び場
  14. そろそろC++やるぞパート13 ポインター ~ ポインターとは ~ - topazの遊び場
  15. そろそろC++やるぞパート14 ポインター ~ ポインターの初期化とメンバーアクセス「->」と「.」の違い ~ - topazの遊び場
  16. そろそろC++やるぞパート15 ポインター ~ スマートポインタ ~ - topazの遊び場
  17. そろそろC++やるぞパート16 ポインター ~ スマートポインタ/unique_ptr ~ - topazの遊び場