【C++言語入門】 第27回 スマートポインタ(unique_ptr)続き


unique_ptr<>についてお話しをしていきますわ
unique_ptr<>は制限があります
unique_ptr<>の概要についてお話ししましたわ
unique_ptr<>には制限がありますわ
- 管理できるメモリ領域は1つまで
- コピーができない
unique_ptr<>はメモリ領域を1つしか保持できません
unique_ptr<>が管理できるメモリ領域は1つまでと決まっていますわ
unique_ptr<>のインスタンスの宣言、初期化時以外では、どのようにしてメモリ領域を割り当てるのだ?
unique_ptr<>のインスタンスに別のメモリ領域を割り当てるには、resetメソッドを使用しますわ
std::unique_ptr::reset(メモリ領域へのポインタ)
resetメソッドにより新しくメモリ領域を割り当てられると、保持していた古いメモリ領域は自動的に開放されますわ
nullptrをセットするか、何も指定しない場合には、単純に、現在保持しているメモリ領域を開放しますわ
resetメソッドで明示的にメモリ領域を開放することができるということか?
unique_ptr<>のテンプレート引数と同じでなければなりませんわ
#include <iostream>
#include <memory>
int main(int argc, char* argv[]) {
std::unique_ptr<int> a(new int(10));
int* pb = new int(5);
a.reset(pb);
std::cout << *a << std::endl;
return 0;
}

int型のメモリ領域を初期値としてunique_ptr<int>のインスタンス"a"を生成していますわ
int型のメモリ領域を作成し、resetメソッドで"a"に管理を委ねていますわ
int型のメモリ領域は自動的に開放されますわ
unique_ptr<int>のインスタンスは、int型のメモリ領域を1つだけしか保持できないのだ
unique_ptr<>のインスタンスに間接演算子""を適用すると、保持しているメモリ領域に間接演算子""を適用するのと同じ効果が得られますわ
*aは、int型の整数の5を返すと云うことか?
deleteされますわ
unique_ptr<>はコピーできません
unique_ptr<>のインスタンスを別のunique_ptr<>のインスタンスに代入することはできませんわ
unique_ptr<>クラスでは、コピーコンストラクタやコピーをするための代入演算子"="が= deleteにより削除定義されていますわ
#include <iostream>
#include <memory>
int main(int argc, char* argv[]) {
std::unique_ptr<int> a(new int(10));
std::unique_ptr<int> b = a;
std::unique_ptr<int> c;
c = a;
return 0;
}

b = aではコピーコンストラクタが呼ばれますが、 これは削除された関数です というエラーが出ていますわ
c = aはコピーのための代入演算子ですが、こちらについても これは削除された関数です というエラーが出ていますわ
unique_ptr<>の最大の特徴で、同じメモリ領域を複数のunique_ptr<>のインスタンスで共有することができないのですわ
共有してはいけません
メモリ領域の移動 move関数を使いましょう
unique_ptr<>のインスタンスで保持されているメモリ領域を別のunique_ptr<>のインスタンスで使いたい場合には、どうすればいいのだ?
unique_ptr<>のインスタンスで保持されているメモリ領域を別のunique_ptr<>のインスタンスに移すための関数moveが用意されていますわ
moveは、このような使い方ができますわ
#include <iostream>
#include <memory>
int main(int argc, char* argv[]) {
std::unique_ptr<int> a(new int(10));
std::unique_ptr<int> b(std::move(a));
std::unique_ptr<int> c = std::move(b);
std::unique_ptr<int> d;
d = std::move(c);
return 0;
}
int型のメモリ領域を初期値として指定していますわ
move関数を初期値として指定していますわ
int型のメモリ領域の管理が"a"から"b"に移るのだ
move関数の戻り値をコピーコンストラクタでコピーしていますわ
int型のメモリ領域の管理、つまり所有権は"b"から"c"に移るのだ
move関数の戻り値を代入演算子で代入していますわ
int型のメモリ領域の管理、つまり所有権は"c"から"d"に移るのだ
関数の戻り値としてのunique_ptr<>
unique_ptr<>の別のインスタンスに移すくらいなら、そのまま元のインスタンスを使い続ければいいのですから...
moveによるメモリ領域の所有権の移動は、関数の引数や戻り値に使う場合に便利ですわ
#include <iostream>
#include <memory>
const int SIZE = 50;
std::unique_ptr<char[]> SetMessage(std::unique_ptr<char[]> text) {
sprintf_s(text.get(), SIZE, "これはテストです。\n");
return std::move(text);
}
int main(int argc, char* argv[]) {
std::unique_ptr<char[]> str(new char[SIZE]);
std::unique_ptr<char[]> message = SetMessage(std::move(str));
std::cout << message.get() << std::endl;
return 0;
}

SetMessage関数の引数にunique_ptr<char[]>を指定していますわ
SetMessage関数の実引数のコピーとなるのだ
unique_ptr<>のインスタンスのコピーはできませんので、実引数にはmove関数を使用していますわ
SetMessage関数の戻り値の型もunique_ptr<>としていますわ
move関数を使ってリターンしていますわ
メソッドでunique_ptr<>のメンバ変数を返す
unique_ptr<>として定義されたメンバ変数をメソッドで返すことも可能なのか?
CircleクラスのMessageメソッドでは、メンバ変数"pmessage_"が保持するメモリ領域をgetメソッドを使って返していましたわ
move関数を使って、メモリ領域の所有権を移動してみましょう
#pragma once
#ifndef CIRCLE_H
#define CIRCLE_H
#include <iostream>
#include <memory>
const double kPI = 3.14159265358979323846;
const int kSize = 50;
/// @brief 円
class Circle {
double diameter_; // 直径
std::unique_ptr<char[]> pmessage_;
public:
Circle() : Circle(10.0) {}
Circle(double diameter) : diameter_(diameter), pmessage_(new char[kSize]) {}
virtual ~Circle() {}
double Diameter() const { return diameter_; }
void Diameter(double diameter) { diameter_ = diameter; }
virtual std::unique_ptr<char[]> Message() noexcept {
auto area = [this]() {
double radius = this->Diameter() / 2;
double a = radius * radius * kPI;
return a;
};
sprintf_s(pmessage_.get(), kSize, "円の面積は%fです。", area());
return std::move(pmessage_);
}
};
#endif // CIRCLE_H
#include <iostream>
#include <memory>
#include "circle.h"
int main(int argc, char* argv[]) {
std::unique_ptr<Circle[]> c(new Circle[2]);
c[1].Diameter(5.0);
std::cout << c[0].Message().get() << std::endl;
std::cout << c[1].Message().get() << std::endl;
return 0;
}

CircleクラスのMessageメソッドの戻り値の型がstd::unique_ptr<char[]>となっているのだ
move関数を使っていますわ
char型の配列の所有権は失われていますわ
Messageメソッドのconst修飾子は削除していますわ
Messageメソッドを再び呼び出すと、"pmessage_"はchar型の配列を保持していないのでエラーの原因となりますわ
move関数を使ってunique_ptr<>の所有権を移動する方法は、お勧めできませんわね
メモリ領域の所有権チェック
unique_ptr<>のインスタンスがメモリ領域を確保しているかどうか、確認する方法はあるのか?
unique_ptr<>のインスタンス名を使うと、bool型の値が返りますわね
std::unique_ptr<int> a(new int(0));
:
if (a) {
:
}
if文の条件式としてunique_ptr<int>のインスタンス"a"をそのまま指定しているのだ
int型のメモリ領域を保持している場合は、trueが返りますわ
make_unique<>関数を使いましょう
unique_ptr<>に渡すメモリ領域を生成するのに、明示的にnewを使っていることに違和感があるのだが...
deleteを明示的に使っていないのだから、newも使わないで済ませたいのだ
unique_ptr<>のインスタンスを生成する関数make_unique<>が追加されましたわ
std::make_unique<クラス名>(初期化値)
unique_ptr<>が管理するメモリ領域の型名を指定しますわ
make_unique<>関数の戻り値は、そのままunique_ptr<>の初期化や代入に使用できますわ
autoを使えるのも便利ですわね
#include <iostream>
#include <memory>
#include "circle.h"
int main(int argc, char* argv[]) {
auto a(std::make_unique<int>(10)); // 初期化値
auto b = std::make_unique<Circle>(10.0); // コピーコンストラクタ
std::unique_ptr<int> c;
c = std::make_unique<int>(0); // 代入
return 0;
}
std::make_unique<クラス名[]>(要素数)
newを使ってメモリ領域を確保してからunique_ptr<>に割り当てるのは面倒ですわ
make_unique<>の方が使い勝手が良いと思うので、積極的に使いましょう
その他のメソッド
unique_ptr<>クラスには様々なメソッドが存在します
今回はreleaseとswapについてお話しします
release
getメソッドと同じく、保持しているメモリ領域へのポインタを返します
getと異なるのは、返したメモリ領域の所有権を破棄することです
つまり、返されたメモリ領域は、deleteなどを使って明示的に破棄する必要があります
#include <iostream>
#include <memory>
int main(int argc, char* argv[]) {
std::unique_ptr<int> a(new int(10));
int* pa = a.release();
delete pa;
return 0;
}
まぁ、自動で管理されているメモリ領域をわざわざプログラマーが管理しなければならないようにするメリットはあまり無いとは思いますが...
swap
引数に指定したunique_ptr<>のインスタンスと保持しているメモリ領域の交換をおこないます
#include <iostream>
#include <memory>
int main(int argc, char* argv[]) {
std::unique_ptr<int> a(new int(10));
std::unique_ptr<int> b(new int(5));
a.swap(b);
std::cout << "aの値は" << *a << "です。\n" << std::endl;
std::cout << "bの値は" << *b << "です。\n" << std::endl;
return 0;
}
まぁ、わざわざメモリ領域を交換する場面は思い浮かばないですが...
ソートをおこなう場合などに使うのでしょうか?
Discussion