ポインターのコレクションの代入演算子 – プログラミング – Home

ポインターのコレクションの代入演算子
 
通知
すべてクリア

[解決済] ポインターのコレクションの代入演算子


DOSKOI-PANDA
 DOSKOI-PANDA
(@DOSKOI-PANDA)
ゲスト
結合: 22年前
投稿: 55
Topic starter  

Win2K VC++6.0 SP5 です。

一般的な要求なのですが、どうしたらよいか解りません。
みなさんはどのようにやっているかをお聞かせ下さい。

複数の型の値を統一的に扱いたいため、値の抽象クラスを定義しました。

#include <string>

class Value {
public:
virtual std::string getType() = 0;
virtual std::string toString() = 0;
virtual const char* c_str() { return toString().c_str(); }
virtual ~Value() {};
};

で、必要な型をラッピングしました。

class BoolValue : public Value {
public:
// 実装は略
private:
bool value;
}

class CharValue : public Value {
public:
// 実装は略
private:
std::string value;
}

class IntValue : public Value {
public:
// 実装は略
private:
int value;
}

で、これらの型をコレクションしたいと思い、基底クラスのポインタのコレクションを
定義しました。
ポインタを使用するとメモリ管理の問題が発生しますが、Valueのインスタンスは
ヒープ上に作成することをユーザに(ドキュメントで)義務づけ、delete はコレクショ
ンの
デストラクター内で行うことにしました。

#include <vector>

class Value;

class Values {
public:
Values() : values();
~Values() {
for (int i = 0; i < values.size(); ++i) {
delete values[i];
}
}
private:
std::vector<Value*> values;
};

で、このクラスに代入演算子を定義することを考えたのですが、
代入してしまうとコレクションの中身である、ポインターが指す
アドレスの内容を複数コレクションで共有することになり、
デストラクターで二重破壊をしてしまうことになります。

vector の要素を auto_ptr で持つという禁断の手法を採用する
ことも考えましたが、やはりやめにしました。
auto_ptr が代入時にポインターの所有権を移動するという部分
だけをパクればよいので、

Value& operator = (const Values& values_) {
values = values_.values;
values_.values.clear(); // 本来 values_.valuesの全要素を 0
クリアすべき
return *this;
}

とやりましたが、これはもちろん const なオブジェクトを変更
しようとしているのでコンパイルは通らないので、

Value& operator = (Values& values_);

と宣言しました。コピーコンストラクターも同様に

Values(Values& values_) : values(values_.values) {}

としました。
が、代入演算子やコピーコンストラクターの引数の const 性が
失われるため、Values をさらにコレクションしている class で
コンパイルエラーが発生するようになってしまいました。
(正確には stl のヘッダ内で)

ポインターのコレクションを作る上で、boost::shared_ptr と
いうものがあるらしいのですが、これはポインターを所有する
参照カウントが 0 になったときに内部のポインターを破壊する
そうで、ということは代入の右辺の const 性は保たれないこと
になります。

ポインターのコレクションで複数クラスのオブジェクトを一括管理
するなんて、ありふれた処理だと思うのですが、やっている人は
どのように実装しているのでしょうか?

職場でネットが参照できないため、レスは24時間単位になりますが
識者の方、お教えいただけたら幸いです。


引用未解決
トピックタグ
monkey
 monkey
(@monkey)
ゲスト
結合: 20年前
投稿: 70
 

「仮想コンストラクタ」(自らのコピーコンストラクタによってオブジェクトを動的に
生成するメンバ関数)
と呼ばれるテクニックが使えます。
参考:More Effective C++, 項目25:コンストラクターと非メンバー関数を仮想化する

// sample.
#include <iostream>

class Value
{
public:
virtual Value* clone() const = 0;
virtual void f() const = 0;
};

class BoolValue : public Value
{
public:
BoolValue* clone() const { return new BoolValue( *this ); }
void f() const { std::cout << Bool << std::endl; }
};

class CharValue : public Value
{
public:
CharValue* clone() const { return new CharValue( *this ); }
void f() const { std::cout << Char << std::endl; }
};

class IntValue : public Value
{
public:
IntValue* clone() const { return new IntValue( *this ); }
void f() const { std::cout << Int << std::endl; }
};

#include <vector>

class Values
{
std::vector< Value* > values;

public:
Values(){}

~Values()
{
for( std::vector< Value* >::size_type i = 0; i < values.size(); ++i ){
delete values[i];
}
}

Values( const Values& rhs )
{
for( std::vector< Value* >::size_type i = 0; i < rhs.values.size();
++i ){
values.push_back( rhs.values[i]->clone() );
}
}

Values& operator = ( const Values& rhs )
{
if( this != &rhs ){
for( std::vector< Value* >::size_type i = 0; i < values.size();
++i ){
delete values[i];
}
values.clear();
for( std::vector< Value* >::size_type i = 0; i < rhs.values.size
(); ++i ){
values.push_back( rhs.values[i]->clone() );
}
}
return *this;
}

void push_back( const Value& elem )
{
values.push_back( elem.clone() );
}

void f() const
{
for( std::vector< Value* >::size_type i = 0; i < values.size(); ++i ){
values[i]->f();
}
}
};

void test( Values& v )
{
Values tmp;
tmp.push_back( BoolValue() );
tmp.push_back( CharValue() );
tmp.push_back( IntValue() );
v = tmp;
}

int main()
{
Values v;
test( v );
v.f();
}

# VC++6.0だと派生クラスのclone関数の戻り値の型をValue*にしないと
# エラーを出したかも知れません。


返信引用
DOSKOI-PANDA
 DOSKOI-PANDA
(@DOSKOI-PANDA)
ゲスト
結合: 22年前
投稿: 55
Topic starter  

monkey さん、ありがとうございます。

Prototype パターンを使って自己複製を行い、
コレクション毎にインスタンスを別に持つという
方法も考えました。これだと2重delete の問題はないし、
代入の右辺は const 性が保てます。が、インスタンスを
複数生成するというのが気に入らなくて廃案にしました。

しかし、スマートポインタを使わない方式としては
上記のやり方が最善かも知れません。
これ以上よい方法が見つからなければ、使わせていただき
ます。


返信引用
monkey
 monkey
(@monkey)
ゲスト
結合: 20年前
投稿: 70
 

>Prototype パターンを使って自己複製を行い、
>コレクション毎にインスタンスを別に持つという
>方法も考えました。これだと2重delete の問題はないし、
>代入の右辺は const 性が保てます。

デザインパターンは詳しくないのでウェブサイトでちょっと調べたところ、「仮想コン
ストラクタ」のテクニックと「Prototypeパターン」の本質は同じもののようですね。


返信引用
undefined name
 undefined name
(@undefined name)
ゲスト
結合: 20年前
投稿: 2
 

> デザインパターンは詳しくないのでウェブサイトでちょっと調べたところ、「仮想コン
> ストラクタ」のテクニックと「Prototypeパターン」の本質は同じもののようですね。

あえて言うなら「Prototypeパターン」は概念的な典型であり、
「仮想コンストラクタ」は言語におけるその実装の一形態だろうか。


返信引用
PATIO
(@patio)
Famed Member
結合: 3年前
投稿: 2660
 

コレクション内部のデータを弄らせたくないのであれば、
仮想コンストラクタパターンが一般的でしょう。
内部データの保護を考えるとインスタンスを別にすると言うのが
一番安全ですからね。
処理コストを考えるとポインタのコピーで済ませたいところですが、
ポインタを渡した時点で内部データの安全性は0に等しいので
処理コストを考えるのか、安全性を考えるのかで二択になると思います。
例え、constポインタを渡すようにしてもconst_castされれば、
どうにでもなってしまうので結局は同じ事ですし。
安全性もOKで処理コストも少ないと言うのは難しいと思います。
ポインタの引渡しで外部では変更不可の実装が出来たとしても
その実装部分の処理コストが大きければ、意味がありませんから。
折衷案ですし、使う側のモラルの問題もありますが、
RefPtrというポインタ参照用のconstポインタで返す関数を作成し、
変更の必要があるときはそのクラスに実装しているMakeCloneで複製して
行いなさいと言う実装はしてみたことがあります。
最低限の処理コストである程度の安全性は保てます。
但し、使う側の意識が低いと穴だらけということになりますけれど。
あとは、SharedPtrとの併用ですかねぇ。
私の場合は自作しましたけれど。


返信引用
PATIO
(@patio)
Famed Member
結合: 3年前
投稿: 2660
 

あらら、ちょっと勘違いしてました。
コレクションそのものの代入演算子なんですね。
そうなるとやっぱりMakeCloneすべきだと思います。
ポインタをコピーしたらコピー先で変更できてしまうので
不許可でしょうから。

後、考えらるとしたらSharedPtrのコレクションにして
コピー先のValuesには複製フラグでも設けておいて
複製フラグが立っていたら更新系の関数を軒並みエラーにするかな。
直接メンバーのポインタを触れないようにしておけば、
仕組み上はconst性は保てそうな気はします。


返信引用
DOSKOI-PANDA
 DOSKOI-PANDA
(@DOSKOI-PANDA)
ゲスト
結合: 22年前
投稿: 55
Topic starter  

PATIO さん、ども。

SharedPtr とは、boost::shared_ptr のことですね。
これを使う方がカッコよく、通っぽいのかなと思ってましたが、
安全性を考えると、深いコピーを行う方がよいのですね。
確かに1つのインスタンスを複数箇所から参照するのは
危ないですもんね。

今日、職場の方で monkey さんの方法で実装してみました。
メモリの問題もなく、代入右辺の const 性も保たれ
上々でありました。

ありがとうございました。


返信引用
PATIO
(@patio)
Famed Member
結合: 3年前
投稿: 2660
 

すいません、自作のクラスと話が混ざってしまってました。
boost::shared_ptrでも良いと思います。
私がSharedPtrと書いているのは参照カウンタを持ったポインタクラスを
そのように(私が)呼んでいるのでそう書いてしまいました。
CZSharedPtrというクラスを自前でこさえていますので
ぽろっと出ちゃいました。


返信引用

返信する

投稿者名

投稿者メールアドレス

タイトル *

プレビュー 0リビジョン 保存しました
共有:
タイトルとURLをコピーしました