【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