コンストラクタからインスタンスメソッドを呼び出す場合には下記のような
注意が必要ですが、そもそもそんな注意が必要な仕様にしてまで呼び出せる
ようにする必要があるのでしょうか?
単純に呼び出せないようにすれば良いのではないでしょうか?
■コンストラクタからのインスタンスメソッド呼び出しの注意
◇C++等の場合
メソッドがサブクラスでオーバーライドされていても、サブクラスの
メソッドではなく自クラスのメソッドが呼び出される。
◇Ruby,Java等の場合
メソッドがサブクラスでオーバーライドされている場合、サブクラスの
メソッドが呼び出されるが、サブクラスでのコンストラクトが完了して
いない可能性がある。
■例
◇C++の場合
・ソースプログラム
#include <string>
#include <iostream>
class SuperClass
{
std::string x_;
public:
SuperClass(const std::string &x):
x_(x)
{
method();
}
virtual ~SuperClass() {}
virtual void method() const {
std::cout << "in SuperClass#method\n";
std::cout << x_ << "\n";
}
};
class SubClass: public SuperClass
{
std::string y_;
public:
SubClass(const std::string &y):
SuperClass(y),
y_(y + "def")
{
}
virtual ~SubClass() {}
virtual void method() const {
std::cout << "in SubClass#method\n";
std::cout << y_ << "\n";
}
};
int main()
{
SubClass obj("abc");
obj.method();
}
・実行結果
in SuperClass#method
abc
in SubClass#method
abcdef
◇Rubyの場合
・ソースプログラム
#/usr/local/bin/ruby
class SuperClass
def initialize(x)
@x = x
method()
end
def method
puts 'in SuperClass#method'
puts @x
end
end
class SubClass < SuperClass
def initialize(y)
super(y)
@y = y + 'def'
end
def method
puts 'in SubClass#method'
puts @y
end
end
obj = SubClass.new('abc')
obj.method
・実行結果
in SubClass#method
nil
in SubClass#method
abcdef
--
pegacorn
C++使いの意見ですが、
記事 <1173408799....@30g2000cwc.googlegroups.com> で
"pegacorn"さんは書きました
> コンストラクタからインスタンスメソッドを呼び出す場合には下記のような
> 注意が必要ですが、そもそもそんな注意が必要な仕様にしてまで呼び出せる
> ようにする必要があるのでしょうか?
> 単純に呼び出せないようにすれば良いのではないでしょうか?
引数の異なる複数のコンストラクタがある場合に、それぞれのコンストラクタに
いちいち初期化コードを記述するのは無駄が多いし保守性が悪くなるので、
共通の初期化ルーチンはメソッドとして用意して、それぞれのコンストラクタから
呼び出す、ということはよくやってます。
例えば、メンバ変数が増えたりした時に、全てのコンストラクタにそのメンバの
初期化コードを書くのは手間がかかりますし、ヘタすると初期化ミスの元ですし。
あまり良い例かわかりませんが、
class array {
int *p;
int s;
init() { p = NULL; s = 0; }
destroy() { delete [] p; }
alloc(int size) { destroy(); p = new int[s = size];}
copy(const aclass &src) { alloc(src.s); memcpy(p, src.p, sizeof(*p)*s);}
public:
array() {init();}
array(int size) {init(); alloc(size);}
array(const array &src) {init(); copy(src);}
~array() { destroy(); }
array &operator=(const array &src) { copy(src); return *this;}
};
みたいな感じ。それぞれの機能を書き下すより効率は悪くなってますが、
それよりも保守性重視ってことで。
PROJECT TEAM DoGA 高津正道 ta...@doga.jp
PROJECT TEAM DoGAのホームページ → http://doga.jp/
3月9日(金) 今日のマーフィーの法則 [収支力学の第1法則]
思いがけない収入があると、思いがけない同額の支出がある。
In article <1173408799....@30g2000cwc.googlegroups.com>, "pegacorn" <subscr...@gmail.com> writes
> コンストラクタからインスタンスメソッドを呼び出す場合には下記のような
> 注意が必要ですが、そもそもそんな注意が必要な仕様にしてまで呼び出せる
> ようにする必要があるのでしょうか?
オブジェクトを作成する際には、何らかの初期化が必要な場合が
多いと思います。例えば、MVCだったら、Model と一緒にView も
作らないといけないとか。
> 単純に呼び出せないようにすれば良いのではないでしょうか?
そうすると、
Hoge hoge = new Hoge();
hoge.init();
と呼ぶことになると思いますが、繁雑だと感じることもあるでしょうね。
コンストラクタに繁雑なコードを書かれると、それをoverride できなく
なる言語が多いと思います。特に、Default constructor を回避できない。
これは、かなり困る。特に「Contructor の引数が異なると初期化の
状況が異なる」なんてのを食らうと...
なので、僕は、
class Hoge extends Fuga {
Hoge() { init();
}
みたいな形で書くことが多いです。
> ■コンストラクタからのインスタンスメソッド呼び出しの注意
> ◇C++等の場合
> メソッドがサブクラスでオーバーライドされていても、サブクラスの
> メソッドではなく自クラスのメソッドが呼び出される。
>
> ◇Ruby,Java等の場合
> メソッドがサブクラスでオーバーライドされている場合、サブクラスの
> メソッドが呼び出されるが、サブクラスでのコンストラクトが完了して
> いない可能性がある。
多重継承したクラスの個別の初期化を、どこでどういう順序で
行うかってのは、決った解決策はありません。なので、
Hoge hoge = new Hoge();
hoge.init_superA();
hoge.init_superB();
hoge.init();
とかいう最低なことをしたくなりますが、この場合は、Factory pattern
を使って、避けるのだと思います。
class Factory_Hoge {
Hoge newHoge() {
Hoge hoge = new Hoge();
hoge.init_superA();
hoge.init_superB();
hoge.init();
return hoge;
もちろん、
class Aho extends Hoge {
void init() {
super.init();
自分の初期化;
}
みたいな形でもいいんですけどね。どうしても、使う方の都合による
初期化が出てくるものなので。
In article <0703091241...@XP.doga.jp>, Masamichi Takatsu <ta...@doga.jp> writes
> 引数の異なる複数のコンストラクタがある場合に、それぞれのコンストラクタに
> いちいち初期化コードを記述するのは無駄が多いし保守性が悪くなるので、
> 共通の初期化ルーチンはメソッドとして用意して、それぞれのコンストラクタから
> 呼び出す、ということはよくやってます。
それは必須だと思います。コンストラクタは入口に過ぎないので、
実際の初期化は別なメソッドで行うべきでしょうね。
---
Shinji KONO @ Information Engineering, University of the Ryukyus
河野真治 @ 琉球大学工学部情報工学科
私も時々やってました。
でも、それってこの件の典型的なはまりパターンではないでしょうか?
・ソースプログラム
#include <string>
#include <iostream>
class SuperClass
{
std::string x_;
public:
SuperClass()
{
initialize();
}
virtual ~SuperClass() {}
virtual void initialize() {
x_ = "abc";
}
virtual void put_property() const {
std::cout << "x_ = " << x_ << "\n";
}
};
class SubClass: public SuperClass
{
std::string y_;
public:
SubClass():
SuperClass()
{
}
virtual ~SubClass() {}
virtual void initialize() {
SuperClass::initialize();
y_ = "def";
}
virtual void put_property() const {
SuperClass::put_property();
std::cout << "y_ = " << y_ << "\n";
}
};
int main()
{
SubClass obj;
obj.put_property();
std::cout << "----\n";
obj.initialize();
obj.put_property();
}
・実行結果
x_ = abc
y_ =
----
x_ = abc
y_ = def
--
pegacorn
In article <1173446801.6...@64g2000cwx.googlegroups.com>, "pegacorn" <subscr...@gmail.com> writes
> > 共通の初期化ルーチンはメソッドとして用意して、それぞれのコンストラクタから
> > 呼び出す、ということはよくやってます。
> 私も時々やってました。
> でも、それってこの件の典型的なはまりパターンではないでしょうか?
だめだめな、C++ を例に出して、
典型的なはまりパターン
というのは、やめて欲しいなぁ~
In article <1173408799....@30g2000cwc.googlegroups.com>, "pegacorn" <su
bscri...@gmail.com> writes
> ◇C++等の場合
> メソッドがサブクラスでオーバーライドされていても、サブクラスの
> メソッドではなく自クラスのメソッドが呼び出される。
> class SuperClass
> {
> SuperClass(const std::string &x):
> x_(x)
> {
> method();
> }
> class SubClass: public SuperClass
> SubClass(const std::string &y):
> SuperClass(y),
> y_(y + "def")
> {
method(); <--- これが必要
> }
この場合は SubClass 側で、SubClass.method() を明示的に呼ぶ
必要があります。C++ のようなコンパイル時に親子関係を解決す
る場合は、method()がoverride されていると知っているのは、
SubClass だけだから。
この書き方だと、SubClass.method() が呼ばれる前には、既に
SuperClass.method()が呼ばれているということなので、注意が
必要です。
> ◇Ruby,Java等の場合
> メソッドがサブクラスでオーバーライドされている場合、サブクラスの
> メソッドが呼び出されるが、サブクラスでのコンストラクトが完了して
> いない可能性がある。
> class SuperClass
> def initialize(x)
> @x = x
> method()
> end
> class SubClass < SuperClass
> def initialize(y)
> super(y)
> @y = y + 'def'
method(); <--- これが必要
> end
こっちの場合は、SuperClass.initialize 中の method() は、SubClass
のmethod を呼び出すので、SuperClass のmethod() で初期化した
いならば、SubClass 側で super method() を明示的に呼ぶ必要が
あると思います。スクリプト言語、インタプリタでは、そうなる
ことが多い。多重継承だと、super が一つに決らないので、困る
ことが多いね。「Effective C++」でも、そのあたりの記述があります。
いずれにせよ、SubClass のコンストラクタに対する、SubClass 用の
初期化メソッドは、SubClass のコンストラクタから明示的に呼ぶ、
ということなんじゃないかな。呼ばれ方の差はあるにしても。
このあたり、実験的に決めることが多いです。
> だめだめな、C++ を例に出して、
> 典型的なはまりパターン
> というのは、やめて欲しいなぁ~
言語はC++でもなんでもいいんだけど、
(1)コンストラクタと同じクラスが持つ初期化メソッドを呼ぶ場合
(2)サブクラスでオーバライドすることのある初期化メソッドを
呼ぶ場合
の両方があることとそれをどう使い分けるかを意識して自分が使う初期
化の方法を決めないとダメですよね。当初はどちらか一方だけ使ってい
たとしても、そのうち両方必要になるかも知れないわけで。なおかつ、
(2)でオーバライドしたものを呼んだ場合、サブクラス側の要素は初期
化されていないかも知れないことも分かってないといけないわけで。
まあうっかりしてはまることはあるかも知れない。 久野
私なら初期化メソッドを2つづつ用意してみてこんな感じにしてみます。
・ソースプログラム
#include <iostream>
#include <string>
class SuperClass;
class SubClass;
class SuperClass {
private:
std::string x_;
void initInstance() {
x_ = "abc";
}
protected:
const std::string& getX() const {
return x_;
}
void setX(std::string x) {
x_ = x;
}
public:
SuperClass() {
initInstance();
}
virtual ~SuperClass() {}
virtual void put_property() const {
std::cout << "x_ = " << x_ << std::endl;
}
virtual void reInitInstance() {
initInstance();
}
};
class SubClass : public SuperClass {
private:
std::string y_;
void initInstance() {
y_ = "def";
}
protected:
const std::string& getY() const {
return y_;
}
void setY(std::string y) {
y_ = y;
}
public:
SubClass() {
initInstance();
}
virtual ~SubClass() {
}
virtual void put_property() const {
SuperClass::put_property();
std::cout << "y_ = " << y_ << std::endl;
}
virtual void reInitInstance() {
SuperClass::reInitInstance();
initInstance();
}
};
int main(int argc, char *argv[]) {
SubClass theInstance;
theInstance.put_property();
std::cout << "----" << std::endl;
theInstance.reInitInstance();
theInstance.put_property();
}
・実行結果
x_ = abc
y_ = def
On 3月10日, 午前1:49, k...@ie.u-ryukyu.ac.jp (Shinji KONO) wrote:
> だめだめな、C++ を例に出して、
>
> 典型的なはまりパターン
>
> というのは、やめて欲しいなぁ~
現在の C++ の仕様だとはまりませんか?という話をしているのに、
そんな事言われても...
実際にこんな感じではまる人は(少なくとも私の周りには)いますし。
まあ、はまる人は大抵この仕様を知らないんですけどね。
> この場合は SubClass 側で、SubClass.method() を明示的に呼ぶ
> 必要があります。C++ のようなコンパイル時に親子関係を解決す
> る場合は、method()がoverride されていると知っているのは、
> SubClass だけだから。
そうすると、2度 SuperClass#method が呼ばれますが、
2度呼ばれると困るような仕様になっている場合は
困りますね。
> こっちの場合は、SuperClass.initialize 中の method() は、SubClass
> のmethod を呼び出すので、SuperClass のmethod() で初期化した
> いならば、SubClass 側で super method() を明示的に呼ぶ必要が
> あると思います。
そうしても、@y が未初期化の状態で SubClass#method が
呼ばれる事には変わりはないし、SubClass#method が
2度呼ばれてしまいますね。
> いずれにせよ、SubClass のコンストラクタに対する、SubClass 用の
> 初期化メソッドは、SubClass のコンストラクタから明示的に呼ぶ、
> ということなんじゃないかな。呼ばれ方の差はあるにしても。
>
> このあたり、実験的に決めることが多いです。
では、河野さんの考えは、
・コンストラクタからインスタンスメソッドを呼び出せる必要がある。
・ただし、各言語の仕様の違いに注意し、
・適切にインスタンスメソッドを設計し、
・適切な順序で呼び出す必要がある。
という感じですね。
でも、まだ
・コンストラクタからインスタンスメソッドを呼び出せる必要がある。
理由がよくわからないのですが、
初期化処理を他のクラスのメソッド等を呼び出す事で行うのではなく、
自身のインスタンスメソッドを呼び出して行わなければならないケースって
具体的にどんなケースがありますか?
--
pegacorn
書籍版 C++ FAQ では、一部例外を除いて初期化並びを使う事を推奨しています。
電子版(comp.lang.c++)に書いてあるかは未確認ですが。
理由はこんな感じ。
・初期化並びを使わないと処理速度が遅くなる。
・const データメンバは初期化並びでしか初期化できない。
・リファレンスデータメンバは初期化並びでしか初期化できない。
--
pegacorn