48

JavaScript や PHP などの動的型付け言語では、次のような関数をよく実行します。

function getSomething(name) {
    if (content_[name]) return content_[name];
    return null; // doesn't exist
}

オブジェクトが存在する場合、または存在しない場合はオブジェクトを返しますnull

参照を使用して C++ で同等のものは何ですか? 一般的に推奨されるパターンはありますか?isNull()この目的のためのメソッドを持ついくつかのフレームワークを見ました:

SomeResource SomeClass::getSomething(std::string name) {
    if (content_.find(name) != content_.end()) return content_[name];
    SomeResource output; // Create a "null" resource
    return output;
}

次に、呼び出し元は次のようにリソースをチェックします。

SomeResource r = obj.getSomething("something");
if (!r.isNull()) {
    // OK
} else {
    // NOT OK
}

ただ、このようなマジックメソッドをクラスごとに実装するのは重そうです。また、オブジェクトの内部状態を「null」から「not null」に設定する必要がある場合も明らかではないようです。

このパターンに代わるものはありますか?ポインターを使用して実行できることは既に知っていますが、参照を使用してどのように/できるか疑問に思っています。それとも、C++ で "null" オブジェクトを返すことをあきらめて、C++ 固有のパターンを使用する必要がありますか? それを行う適切な方法についての提案をいただければ幸いです。

4

8 に答える 8

50

NULL であってはならないため、参照中にこれを行うことはできません。基本的に 3 つのオプションがあり、1 つはポインターを使用し、もう 1 つは値セマンティクスを使用します。

  1. ポインターを使用する (注: これには、呼び出し元がリソースへのポインターを持っている間、リソースが破棄されないことが必要です。また、オブジェクトを削除する必要がないことを呼び出し元が認識していることを確認してください):

    SomeResource* SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return &(*it);  
        return NULL;  
    }
    
  2. std::pairaを使用してbool、アイテムが有効かどうかを示します (注: SomeResource に適切なデフォルト コンストラクターがあり、構築にコストがかからないことが必要です)。

    std::pair<SomeResource, bool> SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return std::make_pair(*it, true);  
        return std::make_pair(SomeResource(), false);  
    }
    
  3. 使用boost::optional:

    boost::optional<SomeResource> SomeClass::getSomething(std::string name) {
        std::map<std::string, SomeResource>::iterator it = content_.find(name);
        if (it != content_.end()) 
            return *it;  
        return boost::optional<SomeResource>();  
    }
    

値のセマンティクスが必要で、Boost を使用できる場合は、オプション 3 をお勧めします。boost::optionaloverの主な利点はstd::pair、単位boost::optional化された値がカプセル化された型を構築しないことです。これは、デフォルトのコンストラクターを持たない型に対して機能し、自明でないデフォルトのコンストラクターを持つ型の時間/メモリを節約することを意味します。

また、(イテレータを再利用して) マップを 2 回検索しないように例を修正しました。

于 2012-04-29T09:34:29.333 に答える
27

なぜ「ポインタを使う以外に」?ポインターの使用、C++で行う方法です。isNull()あなたが言及した関数のようなものを持ついくつかの「オプション」タイプを定義しない限り。(または、のように既存のものを使用しますboost::optional

参照は、 nullにならないように設計され、保証されています。「どうすればそれらをnullにすることができるか」と尋ねることは無意味です。「null許容参照」が必要な場合は、ポインターを使用します。

于 2012-04-29T09:39:29.730 に答える
6

すべてのタイプに特別なメソッドを実装する場合に問題を回避する、比較的非侵入的で優れたアプローチの 1 つは、boost.optionalで使用されるものです。基本的には、保持されている値が「有効」かどうかを確認できるテンプレート ラッパーです。

ところで、これはドキュメントでよく説明されていると思いますが、注意してくださいboost::optionalboolこれは解釈が難しい構造です。

編集:質問は「NULL参照」について尋ねますが、コードスニペットには値で返す関数があります。その関数が実際に参照を返した場合:

const someResource& getSomething(const std::string& name) const ; // and possibly non-const version

その場合、関数はsomeResource、参照されるオブジェクトの寿命が少なくとも参照を返すオブジェクトの寿命と同じである場合にのみ意味があります (そうでない場合は、ぶら下がっている参照が存在することになります)。この場合、ポインタを返すのはまったく問題ないようです。

const someResource* getSomething(const std::string& name) const; // and possibly non-const version

ただし、呼び出し元がポインターの所有権を取得しないこと、およびポインターを削除しようとしないことを明確にする必要があります。

于 2012-04-29T09:34:36.563 に答える
5

これを処理するには、いくつかの方法を考えることができます。

  • 他の人が示唆したように、使用boost::optional
  • オブジェクトが有効でないことを示す状態にする (Yuk!)
  • 参照の代わりにポインターを使用する
  • null オブジェクトであるクラスの特別なインスタンスを持つ
  • 例外をスローして失敗を示します (常に適用されるとは限りません)
于 2012-04-29T09:43:04.333 に答える
4

JavaやC++のC#とは異なり、参照オブジェクトをnullにすることはできません。
したがって、この場合に使用する2つの方法をお勧めします。

1-参照の代わりに、std::shared_ptrなどのnullを持つ型を使用します

2-参照を出力パラメータとして取得し、成功した場合はブール値を返します。

bool SomeClass::getSomething(std::string name, SomeResource& outParam) {
    if (content_.find(name) != content_.end()) 
    {
        outParam = content_[name];
        return true;
    }
    return false;
}
于 2012-04-29T09:40:05.687 に答える
2

以下のコードは、「無効な」参照を返す方法を示しています。これは、ポインターの使用方法が異なるだけです (従来の方法)。

参照を返す関数は常に有効な参照を返すことが期待されるため、他のユーザーが使用するコードでこれを使用することはお勧めしません。

#include <iostream>
#include <cstddef>

#define Nothing(Type) *(Type*)nullptr
//#define Nothing(Type) *(Type*)0

struct A { int i; };
struct B
{
    A a[5];
    B() { for (int i=0;i<5;i++) a[i].i=i+1; }
    A& GetA(int n)
    {
        if ((n>=0)&&(n<5)) return a[n];
        else return Nothing(A);
    }
};

int main()
{
    B b;
    for (int i=3;i<7;i++)
    {
        A &ra=b.GetA(i);
        if (!&ra) std::cout << i << ": ra=nothing\n";
        else std::cout << i << ": ra=" << ra.i << "\n";
    }
    return 0;
}

マクロはNothing(Type)を返します。この場合は、参照のアドレスが設定されている を使用することもできます。このアドレスは、ポインターを使用しているかのように確認できるようになりました。nullptr0

于 2015-04-04T08:37:27.350 に答える
1

ここにいくつかのアイデアがあります:

代替案 1:

class Nullable
{
private:
    bool m_bIsNull;

protected:
    Nullable(bool bIsNull) : m_bIsNull(bIsNull) {}
    void setNull(bool bIsNull) { m_bIsNull = bIsNull; }

public:
    bool isNull();
};

class SomeResource : public Nullable
{
public:
    SomeResource() : Nullable(true) {}
    SomeResource(...) : Nullable(false) { ... }

    ...
};

代替案 2:

template<class T>
struct Nullable<T>
{
    Nullable(const T& value_) : value(value_), isNull(false) {}
    Nullable() : isNull(true) {}

    T value;
    bool isNull;
};
于 2012-04-29T09:46:48.763 に答える