コンストラクタの初期化子newした変数の後始末はどうすべきなのか?

[戻る]
こんちゃん 2012/09/06(木) 16:47:27
大きな配列を定数で初期化する方法としてこのよなコードをネットで拝見しましたが
このコードに疑問があります。
Square() : table(new int[1000])
メンバ変数table を初期化リストで、new 演算子を使い動的にを生成していますが、この
後始末をdelete 演算子を使って明示的に開放する必要はありませんか。
具体的に書くと、これは要らないのでしょうか?
Square()::~Square()
{
        delete []table;
}

// -- square.h --
#pragma once
class Square
{
    int *table;
public:
    Square() : table(new int[1000])
    {
        for (int i = 0; i < 1000; ++i) table[i] = i * i;
    }
    int operator()(const int i) const { return table[i]; }
};

extern const Square square;

// -- global.cpp --
#include "square.h"
#include <iostream>

const Square square;

using namespace std;

int main()
{
    for(int i=0; i<1000; ++i)
    {
        cout << square(i) << endl;
    }

    return 0;
}

tetrapod 2012/09/06(木) 17:48:12
delete[] 必要です。
ただこのコードの場合 ~Square が実施されるのはプログラム終了時で、
Square はメモリ以外のリソースを取っていないため、無くても実害ない、です。
(ファイルとか、ソケットとか、そういう非メモリリソースを取っていると必須)
たまたま<strong>この例では</strong>実害ない
だけで、あってしかるべき。そのコードは説明のための手抜きをしていると推測。

手抜きは他にもあります。一歩進んでみますか?

メンバー配列が2個以上になったときなどに、単純に
Square::Square() : table1(new int [N]), table2(new int [M]) ....
としてしまうといろいろまずい。
table2 の new の際に失敗すると table1 を delete[] する術がない
table1 の new の際に失敗すると table2 が不定値で delete[] に渡すと失敗する
 (グローバル変数でなくした場合)

function-try-block を調べてみるべし。
スマートポインタを調べてみるべし。

こんちゃん 2012/09/06(木) 19:13:59
tetrapod さんありがとうございます。
省略してあった理由が分かりました。
コンストラクタでの例外処理は絶対NGと思い込んでいたので
function-try-blockは大変参考になりました。
スマートポインタ は知っていましたが、当該コードのような配列には使えないですよね?

tetrapod 2012/09/06(木) 20:22:24
デストラクタで例外を投げるのは絶対禁止(捕捉できずに死ぬだけ)だけど
コンストラクタで例外を投げるのは普通のことです。
いや、むしろ「適切」である場合のほうが多い。
「禁じ手」本が間違っているだけ。

提示例にはツッコミどころがいっぱいあるんだけど、あえて目をつぶるとして・・・
new の例外に対して安全にするという1点だけ対処するとしたら、方策は
・ 生ポインタ+function-try-block とか
・ boost::scoped_array<int> とか
・ std::vector<int> とか
・ std::tr1::array<int,N> とか
いくつも思いつくわけで、お好きな方式でどうぞ。

επιστημη [E-Mail] [HomePage] 2012/09/06(木) 21:37:19
>・ 生ポインタ+function-try-block とか
>・ boost::scoped_array<int> とか
>・ std::vector<int> とか
>・ std::tr1::array<int,N> とか

C++11 (VC10,11) なら unique_ptr もオススメ

#include <cstdio>
#include <memory>

class item {
public:
  item() { puts("ctor"); }
  virtual ~item() { puts("dtor"); }
};

class obj {
public:
  obj(int n) : items(new item[n]) {}
private:
  std::unique_ptr<item[]> items;
};

int main() {
  obj x(3);
  // ちゃんと3回 ~item が動く
}

tetrapod 2012/09/10(月) 10:35:08
元ネタは
http://d.hatena.ne.jp/shogicraft/20120217/1329497890

このページで論じているのは定数として使いたい巨大配列を実行時初期化したいが
オーバーヘッドはどうなるか・・・ですな。

プログラムの初期化のタイミングで作られ、終了までずっと使う定数テーブルだから
・定数であるがゆえにコピー代入も考える必要ないし
・失敗するならプログラムは異常終了でいいので例外捕捉も不要
・純粋にテーブルだからメモリ以外のリソースは絶対に使用しないし
・テーブルの破棄はプログラム終了時点でいいのでデストラクタ明示不要
ってことで最初のサンプルになる、ってことで。

コンストラクションのオーバーヘッド
使う際のオーバーヘッド
しか考察対象にしていない、ということで最初の提示コードになるわけですな。
元記事ライターはあえて記事の焦点を絞っているわけですよ。
デストラクタいらないの?例外安全いらないの?って突っ込みは野暮ってもんだ。

こんちゃん 2012/09/12(水) 11:59:22
[[解決]]
みなさんどうもありがとう。

古いスレッドには追加発言できません。