web-dev-qa-db-ja.com

クラスコンストラクター内の定数メンバー変数を定義するC ++

通常、クラスに定数プライベートメンバー変数があり、ゲッターのみがセッターを持たない場合、次のようになります。

_// Example.h
class Example {
    public:
        Example(const int value);
        const int getValue() const;
    private:
        const int m_value;
};


// Example.cpp
#include "Example.h"

Example::Example(const int value)
: m_value(value)
{
}

const int Example::getValue() const
{
    return m_value;
}
_

今私がやろうとしていることは、そのような定数のintメンバー変数を持つことですが、そのように初期化セクションで定義する代わりに:: m_value(value)他のオブジェクトを取得する必要があります-使用しますこの例のベクトル-コンストラクタのパラメータとして、パラメータオブジェクトに基づいてm_valueを設定します。この場合、サイズが0より大きい場合は、ベクターのサイズ+ 1を実行しようとします。つまり、次のようにします。

_Example::Example(std::vector<Example*> myVec)
{
    if (myVec.size()) {
        m_value = myVec.size() + 1;
    }
    else {
        m_value = -1;
    }
}
_

しかし、エラー_uninitialized member 'Example::m_value' with 'const' type 'const int'_が発生し、初期化セクション内でm_valueを初期化すると、エラー_assignment of read-only data-member 'Example::m_value'_が発生します。それらの周り?

Edit: _m_value_を編集できる唯一の方法は、オブジェクト自体の内部にあります(m_valueはプライベートなので)。ゲッターのみがあると、m_valueをコンストラクターで設定されているもの以外に設定できなくなります。定数intをメンバー変数として持つことで何かメリットがありますか?

16
user1632861

静的メンバー関数を使用して、必要な結果を得て、初期化リストでその関数を呼び出します。このような:

_// Example.h
class Example {
    public:
        Example(const int value);
        Example(std::vector<Example*> myVec);

        const int getValue() const;
    private:
        const int m_value;

        static int compute_m_value(::std::vector<Example*> &myVec);
};

// Example.cpp
#include "Example.h"

Example::Example(const int value)
: m_value(value)
{
}

Example::Example(std::vector<Example*> myVec)
: m_value(compute_m_value(myVec))
{
}

const int Example::getValue() const
{
    return m_value;
}

int Example::compute_m_value(::std::vector<Example*> &myVec)
{
    if (myVec.size()) {
        return myVec.size() + 1;
    }
    else {
        return -1;
    }
}
_

この特定のケースでは、関数は非常に単純なので、コンストラクターで三項演算子(別名: m_value(myVec.size() > 0 ? int(myVec.size() + 1) : int(-1))を使用して、初期化時に値を直接計算できます。これは例のように見えたので、必要な答えを計算する方法が非常に複雑な場合でも、問題を解決するための非常に一般的な方法を紹介しました。

一般的な問題は、定数メンバー変数(およびBTWの参照でもあるメンバー変数)必須が初期化リストで初期化されることです。ただし、初期化子は式にすることができます。つまり、初期化子は関数を呼び出すことができます。この初期化コードはクラスにかなり固有であるため、クラスに対してプライベートな(または保護された)関数である必要があります。ただし、クラスが作成される前に値を作成するために呼び出されるため、存在するクラスインスタンスに依存することはできません。したがって、thisポインターはありません。つまり、静的メンバー関数である必要があります。

現在、myVec.size()の型は_std::vector<Example*>::size_t_であり、その型は符号なしです。また、センチネル値として-1を使用していますが、そうではありません。そして、あなたはそれをintに保存していますが、それはとにかくそれを保持するのに適切なサイズではないかもしれません。ベクトルが小さい場合、これはおそらく問題ではありません。しかし、ベクターが外部入力に基づいてサイズを取得する場合、またはベクターのサイズがわからない場合、またはその他の要素がいくつもわからない場合は、これが問題になります。それについて考え、それに応じてコードを調整する必要があります。

31
Omnifarious

まず、変数は、コンストラクタではなくクラス定義ではdefinedです。コンストラクタではinitializedです。

次に、これを行う方法は、コンストラクターが現在行っていることとまったく同じです。初期化子リストからコンストラクターに値を格納します。

Example::Example(std::vector<Example*> myVec)
    : m_value(myVec.size() ? myVec.size() + 1 : -1) {
}
5
Pete Becker

この回答は他のすべての回答の問題に対処します:

この提案は悪いです:

m_value(myVec.size() ? myVec.size() + 1 : -1)

条件演算子は、最終的な選択に関係なく、2番目と3番目のオペランドを共通の型にします。

この場合、size_tおよびintの一般的なタイプはsize_tです。したがって、ベクトルが空の場合、値(size_t)-1int m_valueに割り当てられます。これは範囲外の変換であり、実装定義の動作を呼び出します。


実装定義の動作に依存しないようにするために、コードは次のようになります。

m_value(myVec.size() ? (int)myVec.size() + 1 : -1)

これで、元のコードにあった別の問題が保持されます。myVec.size() >= INT_MAXの場合の範囲外変換。堅牢なコードでは、この問題にも対処する必要があります。

私は個人的に、この範囲テストを実行し、値が範囲外の場合に例外をスローするヘルパー関数を追加することをお勧めします。コードが読みにくくなり始めていますが、ワンライナーは可能です:

m_value( (myVec.empty() || myVec.size() >= INT_MAX) ? -1 : (int)myVec.size() + 1 )

もちろん、この問題をよりきれいに処理する方法は他にもいくつかあります。 size_tにはm_valueを使用し、センチネル値として(size_t)-1を使用するか、できればセンチネル値の必要性を完全に回避します。

0
M.M

2つの基本的なオプションがあります。 1つは、条件演算子を使用することです。これは、次のような単純な条件に適しています。

Example::Example(const std::vector<Example*> &myVec)
  : m_value( myVec.size() ? myVec.size() + 1 : -1)
{}

より複雑な場合は、メンバー関数に計算を委任できます。内部で仮想メンバー関数を呼び出さないように注意してください。これは、構築中に呼び出されるためです。 staticにするのが最も安全です:

class Example
{
  Example(const std::vector<Example*> &myVec)
    : m_value(initialValue(myVec))
  {}

  static int initialValue(const std::vector<Example*> &myVec)
  {
    if (myVec.size()) {
      return myVec.size() + 1;
    } else {
      return -1;
    }
  }
};

もちろん、後者はクラス外の定義でも機能します。スペースとタイピングを節約するために、それらをクラスに配置しました。