ページ 1 / 1
nullptrチェックについて
Posted: 2017年11月03日(金) 14:23
by dowhile
こんにちは。
現在、c++でクラス設計をしながらゲームを作っています。
早速ですが、以下のようなコードがあるとします。
コード:
class Foo
{
public:
BOOL Func() { return TRUE; }
};
int main()
{
Foo* f;
for (;;) {
if (f->Func()) break;
f = new Foo();
}
delete f;
}
このままですと、fはnullptrですので、f->Func()のところで落ちるかと思います。
(まあ普通はこんな書き方しませんが・・・^^;)
そこで、以下のようにします。
コード:
if (f) {
if (f->Func()) break;
}
fのFuncを呼ぶ前に、fのオブジェクトが存在するかどうかをチェックします。
こうすれば、ループ1週目はfのオブジェクトが作成され、二週目でFunc()が呼ばれるようになります。
しかし、毎度毎度このように条件式でオブジェクトが存在するかどうかをチェックするのは非常に非効率かと思います。
そこで、他に何か良い方法があれば教えていただきたいのですが、いかがでしょうか。(というかあるはず!)
ちなみに、c++の知識はある程度はある・・・つもりです。
どうぞよろしくお願いいたします。
Re: nullptrチェックについて
Posted: 2017年11月03日(金) 19:10
by maru
このコードでは f は未初期化状態であり、nullptr ではなく不定です。
if (f) でnull チェックしても意味がありません。
また、このメンバ関数はクラスオブジェクトを参照していないし、仮想関数にもなっていないので、nullptr でも呼び出しできちゃうんです(規格的にはどうか知りませんが...)。
Re: nullptrチェックについて
Posted: 2017年11月03日(金) 20:16
by dowhile
maruさん
本当ですね。
確認したところ、初期化されていないとエラーが出ました(笑)
書き直させて頂きます。
コード:
class FooA
{
public:
BOOL Func() { return TRUE; }
};
class FooB
{
public:
FooA* m_A;
void Update()
{
for (;;) {
if (m_A) {
if (m_A->Func()) break;
}
m_A = new FooA();
}
}
};
int main()
{
FooB fb;
fb.Update();
}
これでどうですかね?
変数名などは変わってますが、やっていることは同じです。
改めて、よろしくお願いいたしますm(__)m
Re: nullptrチェックについて
Posted: 2017年11月03日(金) 22:17
by maru
やっていることが同じなので、回答も同じです。
このコードでは m_A は未初期化状態であり、nullptr ではなく不定です。
if (m_A) でnull チェックしても意味がありません。
コンパイルできたのであれば、実行して動作を確認してから投稿したら如何ですか。
Re: nullptrチェックについて
Posted: 2017年11月04日(土) 00:23
by Dixq (管理人)
メンバ変数の初期化をいちいち初期化リストで一つ一つやらなければならないことや、
ポインタにnullが入る可能性がある設計上でnullチェックをしないといけないのは言語仕様上仕方のないことです。
JAVAのようにメンバ変数を定義するだけでデフォルト値が入ってくれたり、
Objective-Cのようにnull参照をしてもエラーにならなかったりするのは言語によるサポートがあるからです。
C++はメンバ変数の定義時には(staticでなければ)ゴミが入るし、null参照したら落ちるのはそのような言語仕様なのです。
で、工夫できないかと言えばある程度工夫は出来ます。
null確認したくないのであれば、必ずnullにならないような設計にすること。
例えばコンストラクタでnewした後deleteしないとか、
私の場合であればいつもスマートポインタリストにUIパーツやゲームのタスクなどを入れてそれをforで回します。
list<shared_ptr<A>> _list;
_list.add(make_shared<A>());
_list.add(make_shared<A>());
for(auto a : _list){
a->YYY();
}
こうすればnull確認が必要なくなります。
Re: nullptrチェックについて
Posted: 2017年11月04日(土) 00:43
by YuO
一応,nullptrチェックをしない,nullptrだった場合の挙動は一意に定められる,という条件下において,Null-Objectパターンというものがあります。
ただし,
- 仮想関数を利用するため,速度的な効率を求める場合に非null側の呼び出しが多いと逆に不利になること
- nullptrの代わりにnull objectを使うことを必須とする必要があること
- 必ずしも目的となる挙動の一意性を保証できるとは限らないこと
が問題点でしょうか。
Re: nullptrチェックについて
Posted: 2017年11月04日(土) 03:26
by yomi
「非効率」と思ってるということですがどういう意味で言ってるのでしょうか?
例えば
A.ソースに書くのが面倒くさい(見た目的にも)
B.ifの処理が入ることが処理時間の無駄になるのではないか?
などなど
Re: nullptrチェックについて
Posted: 2017年11月04日(土) 05:09
by かずま
dowhile さんが書きました:
書き直させて頂きます。
コード:
class FooA
{
public:
BOOL Func() { return TRUE; }
};
class FooB
{
public:
FooA* m_A;
void Update()
{
for (;;) {
if (m_A) {
if (m_A->Func()) break;
}
m_A = new FooA();
}
}
};
int main()
{
FooB fb;
fb.Update();
}
これでどうですかね?
次のようにすると、FooB::m_A は nullptr に初期化されます。
コード:
int main()
{
FooB *fbp = new FooB();
fbp->Update();
delete fbp;
}
new FooB はダメです。new FooB() です。
参考例
コード:
#include <iostream>
class FooA {
public:
bool Func() { return true; }
};
class FooB {
public:
FooA* m_A;
int a[2];
void Update() {
for (int i = 0 ; i < 5; i++) {
std::cout << "a[0]=" << a[0] << ", a[1]=" << a[1]
<< ", m_A=" << m_A << "\n";
if (m_A) {
if (m_A->Func()) break;
}
m_A = new FooA();
}
}
};
int main()
{
FooB fb;
fb.Update();
putchar('\n');
FooB *fbp1 = new FooB;
fbp1->Update();
putchar('\n');
FooB *fbp2 = new FooB();
fbp2->Update();
delete fbp1;
delete fbp2;
}
VC++ による実行結果
コード:
a[0]=957813, a[1]=998424, m_A=000E9D75
a[0]=1981952, a[1]=167772170, m_A=001E1E38
a[0]=0, a[1]=0, m_A=00000000
a[0]=0, a[1]=0, m_A=001E3E00
gcc による実行結果
コード:
a[0]=-13213, a[1]=0, m_A=0xffffccc0
a[0]=-2144448376, a[1]=1, m_A=0x1802e5088
a[0]=0, a[1]=0, m_A=0x0
a[0]=0, a[1]=0, m_A=0x600064290
new FooB() だと、m_A も a[0] も初期化されています。
Re: nullptrチェックについて
Posted: 2017年11月04日(土) 14:15
by dowhile
皆さんご回答ありがとうございます。
shared_ptr、NullObjectパターン等、参考にさせていただきます。
Re: nullptrチェックについて
Posted: 2017年11月04日(土) 17:44
by Dixq (管理人)
一応誤解のないように補足すると、shared_ptrはnewしたオブジェクトをdeleteする必要のないスマートポインタの一つでnull確認しなくていい質問の回答としては密接に関係あるものではないです。
JAVAやC#などのガベージコレクションのような使い方をC++で可能にするための仕組みです。
私が言いたかったのはタスクやオブジェクトのポインタリストを持って置き、そのリストをループする際にupdateやdrawすることでnull確認の必要がなくなりますということです。
参考まで