std::unique_ptr です。auto_ptr
が deprecated(廃止予定)になりましたので、今後は shared_ptr,
unique_ptr
がスマートポインタの主流になっていくことでしょう。
auto_ptr の後継という意味では
unique_ptr の方がより近いかもしれません。
unique_ptr は下記2つの形式をサポートします。
まずは unique_ptr を使ってみます。shared_ptr とほとんど同じ感じで使用できますが、コピーはできません。moveを使って管理者の譲渡と明示的に行う必要があります。
最初にシンプルな unique_ptr 使用例を紹介します。
unique_ptr を直接使用する例を記載します。
int 型の生成は、以下のように使用します。
std::unique_ptrup(new int);
値の初期化を一緒に行いたい場合、以下のように使用します。下記例は 10 で初期化しています。
std::unique_ptrup(new int(10));
配列を確保する場合は、以下のように使用します。
std::unique_ptrarr_up(new int[5]);
配列の値を初期化と一緒に行いたい場合、以下のように使用します。下記例は {1,2,3,4,5} で初期化しています。
std::unique_ptrarr_up(new int[5]{1,2,3,4,5});
make_unique による例を記載します。
int 型の生成は、以下のように使用します。
auto up = std::make_unique(77);
値の初期化を一緒に行いたい場合、以下のように使用します。下記例は 10 で初期化しています。
auto up = std::make_unique(77);
配列を確保する場合、以下のように使用します。下記例では、配列数 5 です。
make_unique を使って初期化を一緒に行うことはできないようです。
auto arr_up = std::make_unique(5); // 要素はデフォルト初期化(ゼロでない場合あり) for (int i = 0; i < 5; ++i) { arr_up[i] = (i + 1) * 10; }
上記内容を含む c++ プログラムを以下で紹介します。
ソースコード:
["unique_ptr_01.cpp"]
#include <iostream> // for std::cout
#include <new> // for std::nothrow, placement new
#include <memory> // for std::make_unique
/**
* @brief new/delete のシンプルなサンプル(構造体/クラスを使用しない)
*
* - 組み込み型(int)を使って `new` / `delete` / `new[]` / `delete[]` を示す
* - `std::nothrow` と placement new の簡単な例を含む
*/
int main() {
std::cout << "-- uqique_ptr simple examples start --\n";
// 1) 単一 int の std::unique_ptr
{
std::unique_ptr<int> up(new int(10));
std::cout << "*up = " << *up << "\n";
// delete は自動的に行われる
}
// 2) int 配列の std::unique_ptr
{
std::unique_ptr<int[]> arr_up(new int[5]{1,2,3,4,5});
for (int i = 0; i < 5; ++i) {
std::cout << "arr_up[" << i << "] = " << arr_up[i] << "\n";
}
// delete[] は自動的に行われる
}
// 3) 単一 int 生成、std::make_unique 版
{
auto up = std::make_unique<int>(77);
std::cout << "unique_ptr owns = " << *up << "\n";
// delete は自動的に行われる
}
// 4) 配列 int 生成、std::make_unique 配列版(C++14 以降)
{
auto arr_up = std::make_unique<int[]>(5); // 要素はデフォルト初期化(ゼロでない場合あり)
for (int i = 0; i < 5; ++i) {
arr_up[i] = (i + 1) * 10;
}
std::cout << "make_unique<int[]> contents:";
for (int i = 0; i < 5; ++i) {
std::cout << ' ' << arr_up[i];
}
std::cout << '\n';
// delete[] は自動的に行われる
}
std::cout << "-- uqique_ptr simple examples end --\n";
return 0;
}
ビルド方法:
g++ -std=c++17 -Wall -Wextra -O2 unique_ptr_01.cpp -o unique_ptr_01.out
実行結果:
$ ./unique_ptr_01.out -- uqique_ptr simple examples start -- *up = 10 arr_up[0] = 1 arr_up[1] = 2 arr_up[2] = 3 arr_up[3] = 4 arr_up[4] = 5 unique_ptr owns = 77 make_unique<int[]> contents: 10 20 30 40 50 -- uqique_ptr simple examples end -- $
前の節で unique_ptr の簡単な使用方法について記載しました。
でも「本当に解放処理を自動的にやってくれてるのか心配」ですよね。
本節では、unique_ptr で class インスタンスを生成して、delete 無しで自動的にデストラクタが実行されることを確認してみます。
| コンパイラ : | g++ (Ubuntu 13.3.0-6ubuntu2~24.04), | 13.3.0 |
| OS : | Ubuntu 24.04 (WSL) | |
[ソースコード: "unique_ptr.cpp"]
#include <iostream> // cout, endl
#include <string> // string
#include <memory> // unique_ptr
#include <cstdlib> // EXIT_SUCCESS
class Person {
private:
std::string name_;
std::string phone_;
public:
/**
* @brief デフォルトコンストラクタ
* 空の名前と電話番号で委譲コンストラクタを呼ぶ
*/
Person()
: Person("", "") // 委譲コンストラクタ
{
}
/**
* @brief コピーコンストラクタ
* @param rhs コピー元
*/
Person(const Person& rhs)
: Person(rhs.name_, rhs.phone_)
{
}
/**
* @brief 値を指定するコンストラクタ
* @param name 氏名
* @param phone 電話番号
*/
Person(std::string name, std::string phone)
: name_(std::move(name)), phone_(std::move(phone))
{
std::cout << "Person::Person()" << std::endl;
}
/**
* @brief デストラクタ
*/
virtual ~Person()
{
std::cout << "Person::~Person()" << std::endl;
}
/**
* @brief 名前を取得する
* @return 名前への参照
*/
const std::string& getName() const
{
return name_;
}
/**
* @brief 電話番号を取得する
* @return 電話番号への参照
*/
const std::string& getPhone() const
{
return phone_;
}
};
// Person を出力する演算子オーバーロード
std::ostream& operator<<(std::ostream& os, const Person& p)
{
return (os << '(' << p.getName() << ',' << p.getPhone() << ')');
}
/**
* @brief Person をコンソール出力する(null 安全)
* @param person 出力対象の unique_ptr
*/
void coutPerson(const std::unique_ptr<Person>& person)
{
if (person) {
std::cout << *person << std::endl;
} else {
std::cout << "(null)" << std::endl;
}
}
/**
* @brief unique_ptr が指す Person を差し替える(所有権は引数で渡された参照側に残る)
* @param person 差し替え対象の unique_ptr への参照
*/
void replacePerson(std::unique_ptr<Person>& person)
{
person = std::make_unique<Person>("bar_2", "090-****-????");
}
int main()
{
// unique_ptr 生成
std::unique_ptr<Person> foo = std::make_unique<Person>("foo", "090-****-0123");
std::unique_ptr<Person> bar = std::make_unique<Person>("bar", "090-****-5555");
std::unique_ptr<Person> hoge;
// コンソール出力
coutPerson(foo); // (foo,090-****-0123)
coutPerson(bar); // (bar,090-****-5555)
coutPerson(hoge); // (null)
// Person 差し替え
replacePerson(bar);
coutPerson(bar); // (bar_2,090-****-????)
// 所有権の移動
hoge = std::move(foo);
// hoge = foo; // これはコンパイルエラーになる
coutPerson(foo); // (null)
coutPerson(hoge); // (foo,090-****-0123)
return EXIT_SUCCESS;
}
ビルド(例):
$ g++ -Wall -g make_unique.cpp -o make_unique.out
実行結果:
$ ./make_unique.out Person::Person() Person::Person() (foo,090-****-0123) (bar,090-****-5555) (null) Person::Person() Person::~Person() (bar_2,090-****-????) (null) (foo,090-****-0123) Person::~Person() Person::~Person() $
3箇所でクラス Person のデストラクタが実行されている("Person::~Person()" の部分)ことから、unique_ptr が期待通りに解放処理を行っていることがわかります。
unique_ptr で動的配列を使用する例です。
T[]の特殊化が組み込まれており、[]演算子も実装されています。直観的で気持ちよく使えます。
ただし範囲外アクセスに対するチェック機構はありません。配列を使用したい場合、まずはコンテナの使用を検討するべきです。時々実行速度を問題にコンテナの使用を止めている場合がありますが、C++11で採用された
std::array を使用すれば処理速度の問題もありません。
#include <iostream> // cout, endl, EXIT_SUCCESS
#include <string> // string
#include <memory> // unique_ptr
using namespace std;
int main(int argc, char* argv[])
{
// std::unique_ptr の例
// default_delete の特殊化宣言は不要。delete[]が実行される。
// []演算子もある。
size_t n = 10;
std::unique_ptr<int[]> data(new int[n]);
cout << "std::unique_ptr<int> : ";
for (size_t i = 0; i < n; ++i){
data[i] = i;
}
for (size_t i = 0; i < n; ++i){
cout << data[i] << " ";
}
cout << endl;
return EXIT_SUCCESS;
}
c 言語で規定されている malloc, calloc でメモリを動的に取得した場合、解放に使用する関数は delete や delete[] ではなく free を使用します。デアロケータを指定することで unique_ptr のメモリ解放を free で行えるようにします。
#include <iostream> // cout, EXIT_SUCCESS
#include <memory> // shared_ptr, unique_ptr
#include <cstdlib> // malloc, free
using namespace std;
int main()
{
const size_t n = 256;
// unique_ptr<int[]>
std::unique_ptr<int[], decltype(&free)> memory(static_cast<int*> (malloc(n*sizeof(int))), std::free); // "decltype(&free)" は "void(*)(void*)" となるみたい
// std::unique_ptr<int[], void(*)(void*)> memory(static_cast<int*> (malloc(n*sizeof(int))), std::free);
if (memory.get()==nullptr){
// malloc失敗
}
else{
// malloc成功
for (size_t i = 0; i < n; ++i){
memory[i] = (int)i;
}
for (size_t i = 0; i < n; ++i){
cout << memory[i] << ", ";
}
cout << endl;
}
return EXIT_SUCCESS;
}
これは実装例をいくつか書いてみます。しっくりくるやつを使ってみてください。
シンプルにやるとこんな感じでしょうか。
#include <stdio.h> // fopen, fclose #include <stdlib.h> #include <memory> // unique_ptr #pragma warning(disable : 4996) // fopen, fclose の使用がエラーになるため void custom_fclose(FILE* fp){ if (fp != nullptr){ fclose(fp); } } int main(int argc, char* argv[]) { std::unique_ptr<FILE, decltype(&custom_fclose)> fp(fopen("fopen_test.txt", "w"), custom_fclose); // std::unique_ptr<FILE, void(*)(FILE*)> fp(fopen("fopen_test.txt", "w"), custom_fclose); if (fp.get() != nullptr){ // fopen成功 fprintf(fp.get(), "test\n"); } return EXIT_SUCCESS; // ここで custom_fclose が自動実行されます }
Lambda式 で記載するとこんな感じでしょうか。
#include <stdio.h> // FILE, fopen, fclose
#include <stdlib.h>
#include <memory> // unique_ptr
#pragma warning(disable : 4996)
int main(int argc, char* argv[])
{
std::unique_ptr<FILE, void(*)(FILE*)> fp(fopen("fopen_test.txt", "w"), [&](FILE* fp){
if (fp != nullptr){
fclose(fp);
}
});
// 一応ファイルオープンの成功/失敗を確認してからファイルを使用
if (fp.get() != nullptr){
// fopen成功
fprintf(fp.get(), "test\n");
}
return EXIT_SUCCESS;
// ここで custom_fclose が自動実行されます
}
ファクトリーパターン的に unique_ptr を返す関数を作成するならばこんな感じでしょうか。
#include <cstdio> // fopen, fclose
#include <iostream> // cout, EXIT_SUCCESS
#include <memory> // unique_ptr
using namespace std;
#pragma warning(disable : 4996)
std::unique_ptr<std::FILE, void(*)(FILE*)> make_file(const char * filename, const char * flags)
{
// fclose に NULL を渡して実行してはいけない。
// このため下記のような分岐処理が必要となる。
std::FILE * const fp = std::fopen(filename, flags);
return fp ? std::unique_ptr<std::FILE, void(*)(FILE*)>(fp, std::fclose) : std::unique_ptr<std::FILE, void(*)(FILE*)>();
}
int main()
{
auto fp = make_file("hello.txt", "wb");
// fp.get() をチェック
// fopen に失敗していたら fp.get() は NULL
if (fp.get()){
fprintf(fp.get(), "Hello world.");
}
}
私的には Lambda式 を使った2番目のやつが一番シンプルで好きかな。
c++14 で make_unique が追加されました。Visual Studio は 2013 から対応しているようです。
unique_ptr
を使用する場合、いろいろな理由から make_unique を使用することが推奨みたいです。
| コンパイラ : | g++ (Ubuntu 13.3.0-6ubuntu2~24.04), | 13.3.0 |
| OS : | Ubuntu 24.04 (WSL) | |
[ソースコード: "unique_ptr.cpp"]
#include <iostream> // cout, endl
#include <string> // string
#include <memory> // unique_ptr
#include <cstdlib> // EXIT_SUCCESS
class Person {
private:
std::string name_;
std::string phone_;
public:
/**
* @brief デフォルトコンストラクタ
* 空の名前と電話番号で委譲コンストラクタを呼ぶ
*/
Person()
: Person("", "") // 委譲コンストラクタ
{
}
/**
* @brief コピーコンストラクタ
* @param rhs コピー元
*/
Person(const Person& rhs)
: Person(rhs.name_, rhs.phone_)
{
}
/**
* @brief 値を指定するコンストラクタ
* @param name 氏名
* @param phone 電話番号
*/
Person(std::string name, std::string phone)
: name_(std::move(name)), phone_(std::move(phone))
{
std::cout << "Person::Person()" << std::endl;
}
/**
* @brief デストラクタ
*/
virtual ~Person()
{
std::cout << "Person::~Person()" << std::endl;
}
/**
* @brief 名前を取得する
* @return 名前への参照
*/
const std::string& getName() const
{
return name_;
}
/**
* @brief 電話番号を取得する
* @return 電話番号への参照
*/
const std::string& getPhone() const
{
return phone_;
}
};
// Person を出力する演算子オーバーロード
std::ostream& operator<<(std::ostream& os, const Person& p)
{
return (os << '(' << p.getName() << ',' << p.getPhone() << ')');
}
/**
* @brief Person をコンソール出力する(null 安全)
* @param person 出力対象の unique_ptr
*/
void coutPerson(const std::unique_ptr<Person>& person)
{
if (person) {
std::cout << *person << std::endl;
} else {
std::cout << "(null)" << std::endl;
}
}
/**
* @brief unique_ptr が指す Person を差し替える(所有権は引数で渡された参照側に残る)
* @param person 差し替え対象の unique_ptr への参照
*/
void replacePerson(std::unique_ptr<Person>& person)
{
person = std::make_unique<Person>("bar_2", "090-****-????");
}
int main()
{
// unique_ptr 生成
std::unique_ptr<Person> foo = std::make_unique<Person>("foo", "090-****-0123");
std::unique_ptr<Person> bar = std::make_unique<Person>("bar", "090-****-5555");
std::unique_ptr<Person> hoge;
// コンソール出力
coutPerson(foo); // (foo,090-****-0123)
coutPerson(bar); // (bar,090-****-5555)
coutPerson(hoge); // (null)
// Person 差し替え
replacePerson(bar);
coutPerson(bar); // (bar_2,090-****-????)
// 所有権の移動
hoge = std::move(foo);
// hoge = foo; // これはコンパイルエラーになる
coutPerson(foo); // (null)
coutPerson(hoge); // (foo,090-****-0123)
return EXIT_SUCCESS;
}
ビルド(例):
$ g++ -Wall -g make_unique.cpp -o make_unique.out
実行結果:
$ ./make_unique.out Person::Person() Person::Person() (foo,090-****-0123) (bar,090-****-5555) (null) Person::Person() Person::~Person() (bar_2,090-****-????) (null) (foo,090-****-0123) Person::~Person() Person::~Person() $
c++14 で追加された make_unique は 配列型T に対応しています。Visual Studio は 2013 から対応しているようです。
make_shared は本文記載時点では 配列型T に未対応です。 c++20 で対応予定、VS2019 ver.16.3.6 でも未対応です。
unique_ptr
を使用する場合、いろいろな理由から make_unique を使用することが推奨みたいです。
では「1. unique_ptr を使ってみる」で記載したコードを make_unique を使って書き換えてみます。
■評価環境
■参考URL
#include <tchar.h> // _TCHAR, _tmain
#include <iostream> // cout, endl
#include <string> // string
#include <memory> // unique_ptr
using namespace std;
// class Person
class Person {
private:
string name_;
string phone_;
public:
Person()
: Person("", "") // 委譲コンストラクタ、VS2013 OK、VS2012 NG
// : name_(""), phone_("")
{
}
// コピーコンストラクタ
Person(const Person& rhs) // コピーコンストラクタ
: Person(rhs.name_, rhs.phone_) // 委譲コンストラクタ、VS2013 OK、VS2012 NG
// : name_(rhs.name_), phone_(rhs.phone_)
{
}
Person(string name, string phone)
: name_(name), phone_(phone)
{
cout << "Person::Person(), name_ = " << name_ << endl;
}
// デストラクタ
virtual ~Person()
{
cout << "Person::~Person(), name_ = " << name_ << endl;
}
Person& operator=(const Person& rhd) {
name_ = rhd.name_;
phone_ = rhd.phone_;
cout << "operator=, name_ = " << name_ << endl;
return *this;
}
const std::string getName() const
{
return name_;
}
const std::string getPhone() const
{
return phone_;
}
};
// グローバルな演算子オーバーロード */
std::ostream& operator<<(std::ostream& os, const Person& rhs)
{
return (os << '(' << rhs.getName() << ',' << rhs.getPhone() << ')');
}
// Person をコンソール出力
void PrintPerson(Person& person)
{
cout << person << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
const size_t size_n = 3;
unique_ptr<Person[]> persons = make_unique<Person[]>(size_n);
persons[0] = Person("foo", "090-****-0123");
persons[1] = Person("bar", "090-****-5555");
persons[2] = Person("baz", "090-****-6666");
for (int i = 0; i < size_n; ++i) {
PrintPerson(persons[i]);
}
return EXIT_SUCCESS;
}
[実行結果]

無駄なコンストラクタ、デストラクタ、の発生が気になりますが、この動作で正しいです。
本ページの情報は、特記無い限り下記 MIT ライセンスで提供されます。
|
The MIT License (MIT) Copyright © 2014 Kinoshita Hidetoshi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 2025-11-10 | - | "1._unique_ptr_を使ってみる", "5._make_unique" を更新 |
| 2022-07-13 | - | ページデザイン更新 |
| 2019-11-02 | - | 「6. make_unique 配列版」を追加 |
| 2019-10-26 | - | 「5. make_unique」を追加 |
| 2014-03-09 | - | 新規作成 |