1

person クラスの次の単純なコンストラクタ宣言があるとします。

Person(const string& _firstName, const string& _lastName, const string& _id);

指定されたパラメーターが有効であることを確認するために、エレガントでエラーが発生しにくいと見なされる方法は何ですか? 与えられた引数の 1 つだけが無効であっても、タイプ PERSON のオブジェクトに空の文字列が含まれるようにしたいとします。私はこの解決策を思いつきました:

Person::Person(const string& _firstName, const string& _lastName, const string& _id) {
    if (!isValidName(_firstName) || !isValidName(_lastName)) {
                        firstName = ""; lastName = ""; id = "";
    throw InvalidNameException();
}
if (!isValidID(_id)) {
    firstName = ""; lastName = ""; id = "";
    throw InvalidIDException();
}
firstName = _firstName;
lastName = _lastName;
id = _id;
} 

ctor があまりにも肥大化してしまったので、init メソッドを書くことを考えましたが、その解決策はあまり好きではありません。

いくつかの提案を聞きたいです。

4

5 に答える 5

3

isValidName関数とisValidId関数を、文字列をスローまたは返す関数でラップしないのはなぜですか。

std::string
checkedName( std::string const& name )
{
    if ( !isValidName( name ) ) {
        throw InvalidNameException();
    }
    return name;
}

std::string
checkedId( std::string const& id )
{
    if ( !isValidID( id ) ) {
        throw InvalidIDException();
    }
    return id;
}

その後:

Person::Person( std::string const& firstName,
                std::string const& lastName,
                std::string const& id )
    : myFirstName( checkedName( firstName ) )
    , myLastName( checkedName( lastName ) )
    , myId( checkedId( id ) )
{
}

代わりに (ただし、私の意見では読みにくい)、三項演算子を使用できます。

Person::Person( std::string const& firstName,
                std::string const& lastName,
                std::string const& id )
    : myFirstName( isValidName( firstName )
                    ? firstName
                    : throw InvalidNameException() )
    , myLastName( isValidName( lastName )
                    ? lastName
                    : throw InvalidNameException() )
    , myId( isValidID( id )
            ? id
            : throw InvalidIDException() )
{
}

いずれにせよ、文字列が有効でない限り、コンストラクターの本体には入りません。

于 2013-10-18T17:41:30.203 に答える
3

初期化子リストでメンバー変数を初期化し、それらが有効かどうかを確認することをお勧めします。そうでない場合は、例外をスローします。

Person::Person(const string& _firstName, const string& _lastName, const string& _id) : 
    firstName( _firstName ),
    lastName( _lastName ),
    id( _id )
{
    if (!isValidName( firstName ) || !isValidName( lastName ) || !isValidID( id ) ) {
        throw InvalidNameException();
}

メンバー変数は、コンストラクタ本体に入る前に初期化されます。問題のコードは、メンバー変数を空として初期化し、割り当てられたときに再度初期化します。

例外をスローすると、オブジェクトは構築されたと見なされないため、メンバー変数を「クリア」する必要はありません。例外がスローされる前に正常に構築されたメンバー変数は、そのデストラクタが呼び出されます。オブジェクトが例外をスローした場合にスローすると、デストラクタが呼び出されないことに注意してください (完全に作成されていないため)。

于 2013-10-18T17:31:07.380 に答える
0

それはほとんど肥大化していません。ほんの数行です。しかし、コンストラクターでエラー チェックを実行することには常に問題があります。

最善のアプローチは、おそらく、あなたが持っているのと同じように例外をスローし、呼び出し元にエラーの処理方法を決定させることです。

_firstNameそして、引数が無効であると判断した後、なぜand_lastNameを空の文字列に設定しなければならないのか、私には本当に想像できません。

Person::Person(const string& _firstName, const string& _lastName, const string& _id) {
    if (!isValidName(_firstName) || !isValidName(_lastName))
        throw InvalidNameException();
    if (!isValidID(_id))
        throw InvalidIDException();
    firstName = _firstName;
    lastName = _lastName;
    id = _id;
}

それはまったく肥大化していません。

于 2013-10-18T17:28:35.760 に答える
0

コンストラクターからスローすると、オブジェクトは構築されていないと見なされます。その場合、値を空の文字列に設定しても意味がありません。コンストラクターから例外をスローした後にメンバーにアクセスしようとすると未定義の動作になるためです。

Person::Person(const string& _firstName, const string& _lastName, const string& _id)
    : firstName(_firstName), lastName(_lastName), id(_id) {
    if (!isValidName(firstName) || !isValidName(lastName)) throw InvalidNameException();
    if (!isValidID(_id)) throw InvalidIDException();
}
于 2013-10-18T17:28:45.547 に答える
0

懸念事項を分離する必要があると思います。

  • first_nameがなくても意味がありlast_nameますか?
  • first_nameとがlast_nameなくても意味がありidますか?
  • first_namelast_name、またはが無効であることは理にかなっていますかid?

すべての質問に対する答えは「いいえ」だと思います。そのため、3 つのエンティティは、Identifier(または類似の) という抽象的な概念の中に一緒に属していると思います。これは、提供する必要のあるチェックと保証が重要であるためです。

クラスは次のようになります。

class Identifier {

 public:

  static void ValidateName(const std::string& name) {
    if(name.size() <= 0) {
      std::stringstream ss;
      ss<<"Invalid name: "<<name<<" (expected non-empty string)";
      throw std::invalid_argument(ss.str());
    }
  }

  static void ValidateId(const std::string& id) {
    if(id.size() != 10) {
      std::stringstream ss;
      ss<<"Invalid id: "<<id<<" (expected string of length 10)";      
      throw std::invalid_argument(ss.str());
    }
  }

  Identifier(const std::string& first, const std::string& last, const std::string& id)
      : m_first(first),
        m_last(last),
        m_id(id) {
    Identifier::ValidateName(m_first);
    Identifier::ValidateName(m_last);
    Identifier::ValidateId(m_id);
  }

  const std::string& first_name() const {
    return m_first;
  }

  const std::string& last_name() const {
    return m_last;
  }

  const std::string& id() const {
    return m_id;
  }

 private:
  std::string m_first;
  std::string m_last;
  std::string m_id;
};

クラスが設計されている方法を考えると、無効な を作成することは不可能ですIdentifier。すべてのテストに合格するか、Identifierそもそも が作成されないかのいずれかです。

さらに、Identifier一度作成された を無効にする方法はないことに注意してください。Identifierこれにより、 のインスタンスが存在する場合、その存在全体にわたって有効であることが保証されます。

その保証があれば、 ;Personを取るコンストラクターを介してインスタンスを作成できるようになりました。Identifierコンストラクターは決してスローしないため、チェックを行う必要はありません。クラスは次のPersonようになります。

class Person {
 public:
  Person(const Identifier& identifier) noexcept(true)
      : m_identifier(identifier) { }

  const std::string& first_name() const {
    return m_identifier.first_name();
  }

  const std::string& last_name() const {
    return m_identifier.last_name();
  }

  const std::string& id() const {
    return m_identifier.id();
  }

 private:
  Identifier m_identifier;
};

この方法で懸念事項を分離することにより、次のようになります。

  • Identifierクラスに触れることなく、追加のチェックまたはフィールドでクラスを拡張できPersonます。クラスは独立して進化できます。
  • Identifierチェックを繰り返さなくても、他の場所で s を使用できます。たとえば、 を作成Identifierしてデータベースに導入できます。

最終的なコードは次のとおりです。

#include<iostream>
#include<sstream>
#include<stdexcept>

class Identifier {

 public:

  static void ValidateName(const std::string& name) {
    if(name.size() <= 0) {
      std::stringstream ss;
      ss<<"Invalid name: "<<name<<" (expected non-empty string)";
      throw std::invalid_argument(ss.str());
    }
  }

  static void ValidateId(const std::string& id) {
    if(id.size() != 10) {
      std::stringstream ss;
      ss<<"Invalid id: "<<id<<" (expected string of length 10)";      
      throw std::invalid_argument(ss.str());
    }
  }

  Identifier(const std::string& first, const std::string& last, const std::string& id)
      : m_first(first),
        m_last(last),
        m_id(id) {
    Identifier::ValidateName(m_first);
    Identifier::ValidateName(m_last);
    Identifier::ValidateId(m_id);
  }

  const std::string& first_name() const {
    return m_first;
  }

  const std::string& last_name() const {
    return m_last;
  }

  const std::string& id() const {
    return m_id;
  }

 private:
  std::string m_first;
  std::string m_last;
  std::string m_id;
};


class Person {
 public:
  Person(const Identifier& identifier) noexcept(true)
      : m_identifier(identifier) { }

  const std::string& first_name() const {
    return m_identifier.first_name();
  }

  const std::string& last_name() const {
    return m_identifier.last_name();
  }

  const std::string& id() const {
    return m_identifier.id();
  }

 private:
  Identifier m_identifier;
};


int main() {
  Identifier id("John", "Doe", "1234567890");

  Person p(id); // cannot throw because id has already been
                // constructed

  std::cout<<p.last_name()<<", "<<p.first_name()<<" Id: "<<p.id()<<std::endl;

  try {
    Identifier id2("Sue", "Smith", "126789");
    Person p2(id2);
    std::cout<<p2.first_name()<<std::endl;
  } catch(const std::exception &e) {
    std::cout<<e.what()<<std::endl;
  }

  try {
    Identifier id3("", "Smith", "126789");
    Person p3(id3);
    std::cout<<p3.first_name()<<std::endl;
  } catch(const std::exception &e) {
    std::cout<<e.what()<<std::endl;
  }


  return 0;
}

次のコマンドを使用してコンパイルできます (OS X 10.7.4 の GCC 4.8.1)

g++ validated_names.cpp -std=c++11 -Wall -Wextra

次の出力が生成されます。

./a.out 
Doe, John Id: 1234567890
Invalid id: 126789 (expected string of length 10)
Invalid name:  (expected non-empty string)
于 2013-10-18T18:51:23.710 に答える