メインコンテンツ

カプセル化されたデータ メンバーに定数ではないハンドルが返されています

メソッドにより、オブジェクトの内部メンバーへのポインターまたは参照が返される

説明

この欠陥は、以下の場合に発生します。

  • クラス メソッドにより、データ メンバーのハンドルが返される。ハンドルにはポインターと参照が含まれます。

  • メソッドがデータ メンバーよりもアクセスしやすくなっている。たとえば、メソッドにはアクセス指定子 public があり、一方データ メンバーは privateprotected となっている場合などです。

リスク

アクセス指定子により、クラス メンバーへのアクセスのしやすさが決定されます。たとえば、クラス メンバーが private アクセス指定子とともに宣言されていると、クラス外からはアクセスできません。したがって、非メンバーの非 friend 関数によってメンバーに変更を加えることはできません。

クラス メソッドにより、よりアクセスしにくいデータ メンバーのハンドルが返されると、そのメンバーへのアクセスのしやすさが変わります。たとえば、public メソッドによって private データ メンバーを指すポインターが返される場合、そのデータ メンバーは実質的に private ではなくなります。public メソッドを呼び出す非メンバーの非 friend 関数により、返されたポインターを使用して、そのデータ メンバーの表示や変更を行うことができます。

また、オブジェクトのデータ メンバーを指すポインターを別のポインターに割り当てると、そのオブジェクトを削除する際に、後者のポインターが宙ぶらりんのまま残されることがあります。後者のポインターは、もう存在しなくなっているオブジェクトの部分を指しています。

修正方法

1 つの修正方法として、クラス メソッドからデータ メンバーのハンドルを返すことを回避します。データ メンバーを値で返すことにより、メンバーのコピーが返されるようにします。コピーを変更しても、データ メンバーは変更されません。

ハンドルを返さなければならない場合は、メソッドの戻り値の型に const 修飾子を使用して、ハンドルでデータ メンバーの参照が許可され変更は許可されないようにします。

すべて展開する

#include <string>
#define NUM_RECORDS 100

struct Date {
    int dd;
    int mm;
    int yyyy;
};


struct Period {
    Date startDate;
    Date endDate;
};

class DataBaseEntry {
private:
    std::string employeeName;
    Period employmentPeriod;
public:
    Period* getPeriod(void);
};

Period* DataBaseEntry::getPeriod(void) {
    return &employmentPeriod;
}


void use(Period*);
void reset(Period*);

int main() {
    DataBaseEntry dataBase[NUM_RECORDS];
    Period* tempPeriod;
    for(int i=0;i < NUM_RECORDS;i++) {
        tempPeriod = dataBase[i].getPeriod();
        use(tempPeriod);
        reset(tempPeriod);
    }
    return 0;
}

void reset(Period* aPeriod) {
       aPeriod->startDate.dd = 1;
       aPeriod->startDate.mm = 1;
       aPeriod->startDate.yyyy = 2000;
}

この例では、employmentPeriodDataBaseEntry に対して private です。したがって、非メンバーの非 friend 関数による変更は受け付けません。しかし、employmentPeriod へのポインターが返されると、このカプセル化は壊れます。たとえば、非メンバー関数 resetemploymentPeriod のメンバー startDate を変更します。

修正: メンバーを値で返す

1 つの修正方法として、データ メンバー employmentPeriod を、ポインターではなく値で返します。戻り値はデータ メンバーのコピーであるため、戻り値を変更してもデータ メンバーは変更されません。

#include <string>
#define NUM_RECORDS 100

struct Date {
    int dd;
    int mm;
    int yyyy;
};


struct Period {
    Date startDate;
    Date endDate;
};

class DataBaseEntry {
private:
    std::string employeeName;
    Period employmentPeriod;
public:
    Period getPeriod(void);
};

Period DataBaseEntry::getPeriod(void) {
    return employmentPeriod;
}


void use(Period*);
void reset(Period*);

int main() {
    DataBaseEntry dataBase[NUM_RECORDS];
    Period tempPeriodVal;
    Period* tempPeriod;
    for(int i=0;i < NUM_RECORDS;i++) {
        tempPeriodVal = dataBase[i].getPeriod();
        tempPeriod = &tempPeriodVal;
        use(tempPeriod);
        reset(tempPeriod);
    }
    return 0;
}

void reset(Period* aPeriod) {
       aPeriod->startDate.dd = 1;
       aPeriod->startDate.mm = 1;
       aPeriod->startDate.yyyy = 2000;
}

結果情報

グループ: オブジェクト指向
言語: C++
既定値: オフ
コマンド ライン構文: BREAKING_DATA_ENCAPSULATION
影響度: Medium

バージョン履歴

R2015b で導入