C++プログラミングデバッグの秘訣---読書ノート(6)
7693 ワード
六、いくつかの雑種
1、コピーコンストラクタと付与オペレータの作成を避ける.デフォルトバージョンが適用されない場合、コピーコンストラクタと付与オペレータをプライベートとして宣言し、クラスインスタンスのコピーを禁止することを考慮する.
2、構造関数にコードを書くことを避ける
構造関数が必要な理由はいくつかあります.
a、ベースクラスでは、仮想構造関数を宣言する必要がある場合があります.これにより、ベースクラスを指すポインタを使用して派生クラスのインスタンスを指すことができます.
b、派生クラスでは、構造関数を仮想関数として宣言する必要はありませんが、可読性を高めるためにも、そうすることができます.
c、解析関数が異常を投げ出さないことを宣言する必要がある場合がある
次に、なぜ構造関数が空であるべきかについて説明します.
このStudioクラスを見てください.彼は構造関数でいくつかのリソースを取得したので、構造関数から解放する必要があります.
しかし、問題が発生しました.このクラスの設計--個人の説明を表す新しい要素(number_など)を追加するたびに場合、構造関数に対応するクリーンアップコードを追加する必要があります.これは、「プログラマーに何かを覚えさせないでください」という原則に反します.もう一つの問題は、セキュリティチェックが欠けていることです.まずnumber_0より大きい必要があるかもしれませんが、newの前でif判断を行わなければなりません.もう一つの極端な場合:
Cではthrowに異常が発生したためclass Cの解析関数が実行されず,いくつかの問題が発生した.
次の形式に変更すると、より良いです.
2番目のセキュリティチェックで異常が放出された場合でもname_を実行します.のインテリジェントポインタの構造関数は依然として呼び出され、彼のクリーンアップ作業が実行されます.もう1つの利点は、これらのスマートポインタをNULLに初期化する必要がないことです.これは自動的に完了します.従って、構造関数が異常を放出することは潜在的な危険行為であることがわかる.対応する構造関数は呼び出されません.構造関数が空の関数でない限り、問題が発生する可能性があります.
3、一致した比較オペレータを作成する
よく使うオペレータは<><=>====!=,C++の観点から、これらの操作は6つの相互に完全に独立した関数と書くことができるが、x 1>x 2が成立し、x 2=x 2も成立するなど、相互に関連している.そのため、この目標を達成するためにいくつかのステップが必要です(前のscpp_types.hに追加します):
テストコード(vs 2012+win 7環境):
4、標準C関数ライブラリの使用エラー
C関数ライブラリでは、いくつかの点で安全ではなく、プログラムのクラッシュを引き起こす可能性があります.
例えばstdioを用いる.hでsprintf()のような関数を考えると、次のような問題があるかもしれません.
a、一部の関数は文字配列のポインタ(char*)を受け入れ、合法的なC文字列を指すポインタではなくNULLを渡すと崩壊する(strlen(NULLなど).秒でプログラムを落とすことができます)
b、一部の関数はバッファに書き込まれ、バッファの末尾を越えたメモリを書き換える可能性があり、予期せぬプログラム動作を招く可能性がある.
.....
上記の問題は、次のように処理できます.
a、必要なすべてのセキュリティチェックを実行するバージョンを提供し、空文字列を処理するのと同じ方法でNULLポインタを処理する
b、文字列の操作速度を犠牲にできないアプリケーションに対して、テスト時にのみアクティブ化される一時的なセキュリティチェックの関数バージョンを提供する
......
しかし、これらの問題の最良の解決策は、C文字列関数ライブラリではなく、C++が提供するクラスに変更することです.たとえば、次のようにします.
strlen(name)はnameに変更できる.size()ではstrcpy()に対して文字列付与オペレータのみを使用できます.strcat()またはstrncat()は、次のように変更できます.
あるいは、より短い形式を採用します.
これらのコードは読みやすく、安全であるだけでなく、長い文字列に対してもstrcat()よりも速くなります.割り当てと書き換えが必要なバッファが存在しないため
1、コピーコンストラクタと付与オペレータの作成を避ける.デフォルトバージョンが適用されない場合、コピーコンストラクタと付与オペレータをプライベートとして宣言し、クラスインスタンスのコピーを禁止することを考慮する.
2、構造関数にコードを書くことを避ける
構造関数が必要な理由はいくつかあります.
a、ベースクラスでは、仮想構造関数を宣言する必要がある場合があります.これにより、ベースクラスを指すポインタを使用して派生クラスのインスタンスを指すことができます.
b、派生クラスでは、構造関数を仮想関数として宣言する必要はありませんが、可読性を高めるためにも、そうすることができます.
c、解析関数が異常を投げ出さないことを宣言する必要がある場合がある
次に、なぜ構造関数が空であるべきかについて説明します.
class Student
{
public:
Student
{
if (!number_)
{
number_ = new int(age);
}
}
~Student()
{
if (number_)
{
delete number_;
}
}
private:
int* number_;
};
このStudioクラスを見てください.彼は構造関数でいくつかのリソースを取得したので、構造関数から解放する必要があります.
しかし、問題が発生しました.このクラスの設計--個人の説明を表す新しい要素(number_など)を追加するたびに場合、構造関数に対応するクリーンアップコードを追加する必要があります.これは、「プログラマーに何かを覚えさせないでください」という原則に反します.もう一つの問題は、セキュリティチェックが欠けていることです.まずnumber_0より大きい必要があるかもしれませんが、newの前でif判断を行わなければなりません.もう一つの極端な場合:
#include "stdafx.h"
#include "iostream"
#include "string"
class A
{
public:
A() { std::cout << "Creating A" << std::endl; }
~A(){ std::cout << "Destroying A" << std::endl; }
};
class B
{
public:
B() { std::cout << "Creating B" << std::endl; }
~B(){ std::cout << "Destroying B" << std::endl; }
};
class C : public A
{
public:
C()
{
std::cout << "Creating C" << std::endl;
throw "Don't like C";
}
~C(){ std::cout << "Destroying C" << std::endl; }
private:
B b_;
};
int _tmain(int argc, _TCHAR* argv[])
{
try
{
C c;
}
catch (...)
{
std::cout << "Caught an exception" << std::endl;
}
return 0;
}
Cではthrowに異常が発生したためclass Cの解析関数が実行されず,いくつかの問題が発生した.
次の形式に変更すると、より良いです.
class Student
{
public:
Student(const int number, const char* name)
{
SCPP_ASSERT(number > 0, "number must be large 0");
number_ = new int(number);
SCPP_ASSERT(name != NULL, "name must be not null");
name_ = new std::string(name);
}
~Student()
{
}
private:
Student(const Student& student);
Student& operator = (const Student&);
scpp::ScopedPtr<int> number_;
scpp::ScopedPtr<std::string> name_;
};
2番目のセキュリティチェックで異常が放出された場合でもname_を実行します.のインテリジェントポインタの構造関数は依然として呼び出され、彼のクリーンアップ作業が実行されます.もう1つの利点は、これらのスマートポインタをNULLに初期化する必要がないことです.これは自動的に完了します.従って、構造関数が異常を放出することは潜在的な危険行為であることがわかる.対応する構造関数は呼び出されません.構造関数が空の関数でない限り、問題が発生する可能性があります.
3、一致した比較オペレータを作成する
よく使うオペレータは<><=>====!=,C++の観点から、これらの操作は6つの相互に完全に独立した関数と書くことができるが、x 1>x 2が成立し、x 2
#ifndef __SCCP_TYPES_H__
#define __SCCP_TYPES_H__
#include <ostream>
#include "scpp_assert.h"
template <typename T>
class TNumber
{
public:
TNumber(const T& x =0 )
:data_(x)
{
}
operator T() const
{
return data_;
}
TNumber &operator = (const T& x)
{
data_ = x;
return *this;
}
TNumber operator ++(int)
{
TNumber<T> copy(*this);
++data_;
return copy;
}
TNumber operator ++()
{
++data_;
return *this;
}
TNumber& operator += (T x)
{
data_ += x;
return *this;
}
TNumber& operator -= (T x)
{
data_ -= x;
return *this;
}
TNumber& operator *= (T x)
{
data_ *= x;
return *this;
}
TNumber& operator /= (T x)
{
SCPP_ASSERT(x != 0, "Attempt to divide by 0");
data_ /= x;
return *this;
}
T operator / (T x)
{
SCPP_ASSERT(x != 0, "Attempt to divide by 0");
return data_ / x;
}
private:
T data_;
};
typedef long long int64;
typedef unsigned long long unsigned64;
typedef TNumber<int> Int;
typedef TNumber<unsigned> Unsigned;
typedef TNumber<int64> Int64;
typedef TNumber<unsigned64> Unsigned64;
typedef TNumber<float> Float;
typedef TNumber<double> Double;
typedef TNumber<char> Char;
class Bool
{
public:
Bool(bool x = false)
:data_(x)
{
}
operator bool () const
{
return data_;
}
Bool& operator = (bool x)
{
data_ = x;
return *this;
}
Bool& operator &= (bool x)
{
data_ &= x;
return *this;
}
Bool& operator |= (bool x)
{
data_ |= x;
return *this;
}
private:
bool data_;
};
inline std::ostream& operator << (std::ostream& os, Bool b)
{
if (b)
{
os << "True";
}
else
{
os << "False";
}
return os;
}
#define SCPP_DEFINE_COMPARISON_OPERATORS(Class) \
bool operator < (const Class& that) const { return CompareTo(that) < 0; } \
bool operator > (const Class& that) const { return CompareTo(that) > 0; } \
bool operator ==(const Class& that) const { return CompareTo(that) ==0; } \
bool operator <=(const Class& that) const { return CompareTo(that) <=0; } \
bool operator >=(const Class& that) const { return CompareTo(that) >=0; } \
bool operator !=(const Class& that) const { return CompareTo(that) !=0; }
#endif
テストコード(vs 2012+win 7環境):
#include "stdafx.h"
#include "scpp_assert.h"
#include "iostream"
#include "scpp_vector.h"
#include "scpp_array.h"
#include "scpp_matrix.h"
#include "algorithm"
#include "scpp_types.h"
#include "scpp_refcountptr.h"
#include "scpp_scopedptr.h"
#include "scpp_ptr.h"
#include "string"
#define STUDENTAGE 10
class Student
{
public:
Student(int age)
{
SCPP_ASSERT(age > 0, "number must be large 0");
age_ = age;
}
int CompareTo(const Student& that) const
{
if (this->age_ > that.age_)
{
return 1;
}
else if(this->age_ == that.age_)
{
return 0;
}
else
{
return -1;
}
}
SCPP_DEFINE_COMPARISON_OPERATORS(Student);
private:
int age_;
};
int _tmain(int argc, _TCHAR* argv[])
{
Student studentFirst(STUDENTAGE);
Student studentSecond(STUDENTAGE);
if (studentFirst >= studentSecond)
{
std::cout << "(studentFirst > studentSecond) && (studentFirst == studentSecond)" << std::endl;
}
return 0;
}
4、標準C関数ライブラリの使用エラー
C関数ライブラリでは、いくつかの点で安全ではなく、プログラムのクラッシュを引き起こす可能性があります.
例えばstdioを用いる.hでsprintf()のような関数を考えると、次のような問題があるかもしれません.
a、一部の関数は文字配列のポインタ(char*)を受け入れ、合法的なC文字列を指すポインタではなくNULLを渡すと崩壊する(strlen(NULLなど).秒でプログラムを落とすことができます)
b、一部の関数はバッファに書き込まれ、バッファの末尾を越えたメモリを書き換える可能性があり、予期せぬプログラム動作を招く可能性がある.
.....
上記の問題は、次のように処理できます.
a、必要なすべてのセキュリティチェックを実行するバージョンを提供し、空文字列を処理するのと同じ方法でNULLポインタを処理する
b、文字列の操作速度を犠牲にできないアプリケーションに対して、テスト時にのみアクティブ化される一時的なセキュリティチェックの関数バージョンを提供する
......
しかし、これらの問題の最良の解決策は、C文字列関数ライブラリではなく、C++が提供するクラスに変更することです.たとえば、次のようにします.
strlen(name)はnameに変更できる.size()ではstrcpy()に対して文字列付与オペレータのみを使用できます.strcat()またはstrncat()は、次のように変更できます.
int _tmain(int argc, _TCHAR* argv[])
{
std::ostringstream buffer;
buffer << "zeng";
buffer << "raoli";
std::string result = buffer.str();
std::cout << "this result string is : " << result << std::endl;
return 0;
}
あるいは、より短い形式を採用します.
int _tmain(int argc, _TCHAR* argv[])
{
std::string result = "zeng";
result += "raoli";
std::cout << "this result string is : " << result << std::endl;
return 0;
}
これらのコードは読みやすく、安全であるだけでなく、長い文字列に対してもstrcat()よりも速くなります.割り当てと書き換えが必要なバッファが存在しないため