第2章コンストラクタとデストラクタ
継承
オブジェクトは、実際に必要なときに作成されます。
オブジェクトの作成(または破棄)は、親オブジェクトとメンバーオブジェクトの再帰的な作成(破棄)をトリガーします。複雑な階層でのオブジェクトの複合使用に注意してください。それらは作成と破壊をより高価にします。
明示的な構成を使用してメンバー変数を初期化します。
class FTest
{
public:
FTest(const std::wstring &str)
:_str(str)//建议
{
// _str = str;//不建议
}
private:
std::wstring _str;
};
第3章仮想関数
仮想関数の構築
- コンストラクターはvptr(仮想関数テーブルポインター)を初期化する必要があります
- 仮想関数はポインタを介して間接的に呼び出されるため、最初に仮想関数テーブルへのポインタを取得してから、正しい関数オフセットを取得する必要があります
- インライン化はコンパイル時に決定され、コンパイラーが実行時に解決される仮想関数をインライン化するように設定することはできません。
テンプレートと継承
実行時にのみ解決できる仮想関数は、インライン化を使用できません。関数呼び出しの動的バインディングは継承の結果であるためです。したがって、動的バインディングを排除する1つの方法は、継承の代わりにテンプレートベースの設計を使用することです。テンプレートは、解析ステップを実行時からコンパイル時に進めます
スレッドセーフな文字列クラスの場合
準備
class Locker
{
public:
Locker(){
}
virtual ~Locker(){
}
virtual void lock() = 0;
virtual void unlock() = 0;
};
class CirticalSectionLocker : public Locker
{
public:
CirticalSectionLocker(){
}
~CirticalSectionLocker(){
}
virtual void lock()override
{
//临界区方式加锁
}
virtual void unlock()override
{
//临界区方式解锁
}
};
class MutexLocker : public Locker
{
public:
MutexLocker(){
}
~MutexLocker(){
}
virtual void lock()override
{
//互斥锁方式加锁
}
virtual void unlock()override
{
//互斥锁方式解锁
}
};
class SemaphoreLocker : public Locker
{
public:
SemaphoreLocker(){
}
~SemaphoreLocker(){
}
virtual void lock()override
{
//信号量方式加锁
}
virtual void unlock()override
{
//信号量方式解锁
}
};
文字列クラスからの派生に基づいて、
サンプルコードは、理解を助けるために、次の3つの観点から設計されており、文字列クラスからCriticalSectionString、MutexString、およびSemahoreStrintgを派生するようにコンパイルおよび
ハードコーディングできない場合があります
。各クラスは、独自の同期メカニズムを実装しています。
class MutexString : public std::string
{
public:
int getLength()
{
int length = 0;
_mutexLocker.lock();
length = std::string::length();
_mutexLocker.unlock();
return length;
}
private:
MutexLocker _mutexLocker;
}
この設計には、パフォーマンス上の利点があります。仮想関数を介して正しいロックおよびロック解除メソッドを呼び出します。ただし、この設計の欠点は、同期メカニズムごとに独自の文字列クラスを作成する必要があることです。コードの再利用性の低下につながります。
継承
別のThreadSafeStringクラスを派生させます。LoCKerオブジェクトへのポインタが含まれており、ポリモーフィックメカニズムを介した操作中に特定の同期メカニズムが選択されます。
class ThreadSafeString : public string
{
public:
ThreadSafeString(const char *str,Locker *locker)
:std::string(str),_locker(locker)
{
}
int getLength()
{
_locker->lock();//未进行判空
int length = std::string::length();
_locker->unlock();
return length;
}
private:
Locker* _locker;
}
{
MutexLocker locker = new MutexLocker ;
ThreadSafeString safeStr("ABC",locker);
}
仮想関数呼び出しのロックとロック解除は実行中にのみ解決されるため、インライン化することはできません。これにより、パフォーマンスが低下します。ロッカータイプのパラメータ化後に取得される、テンプレートに基づく
テンプレート
文字列クラス
template<class LOCKER>
class ThreadSafeString : public string
{
public:
ThreadSafeString(const char *str)
:std::string(str)
{
}
int getLength();
private:
LOCKER _locker;
};
template<class LOCKER>
inline int ThreadSafeString<LOCKER>::getLength()
{
_locker.lock() :
int length = std::string::length();
_locker.unlock();
return length;
}
{
ThreadSafeString<MutexLocker> safeString = "Hello":
int len = safeString.getLength();
}
この設計により、ロックおよびロック解除のための仮想関数呼び出しも回避されます。ThreadSafeStringは、テンプレートをインスタンス化するときに特定の同期タイプを選択することを宣言します。ハードコーディングと同様に、コンパイラはこれら2つの仮想関数呼び出しを解析してインライン化できます。
テンプレートの計算は実行期間からコンパイル期間まで事前に行われ、コンパイル時にインライン化が使用されるため、パフォーマンスが向上します。
第4章戻り値の最適化
オブジェクトを値で返す必要がある場合は、RVOを使用してローカルオブジェクトを作成および破棄する手順を省略できるため、パフォーマンスが向上します。
第5章一時的なオブジェクト
値渡し
class Test
{
public:
Test(){
}
~Test(){
}
};
void functionPassValue(Test test)
{
//....
}//此种方式 编译器将创建一个Test类型的临时对象。并且使用test作为输入参数来复制构造它(临时对象)。然后临时对象作为实参传递给functionPassValue 该新创建的临时对象将按引用方式传递给functionPassValue
void functionPassReferences(Test &test)
{
//...
}
void functionPassPointer(Test *test)
{
}//按照指针以及引用方式不会产生临时对象。
一時オブジェクトの作成と破棄のコストは比較的高くなります。可能であれば、一時オブジェクトの生成を回避するために、オブジェクトはポインタまたは参照によって渡される必要があります
値で返す
(参照やポインターではなく)値でオブジェクトを返す関数を作成すると、一時オブジェクトが生成される可能性があります。
std :: wstring getWStringByReturnValue()
{ std :: wstring str; // ... return str; } //コンパイラは、戻り値を格納するための一時オブジェクトを生成します。std :: stringの+演算子
std::string s1 = "Hello";
std::string s2 = "World";
std::string s3;
s3 = s1 + s2;//产生一个临时对象
std::string s3 = s1+s2;//不会产生临时对象
一時オブジェクトが生成されるのはなぜですか?
s3の古いコンテンツを変更し、s1 + s2の新しいコンテンツを使用して上書きする権限がないためです。代入演算子
(=)は、s3を古いコンテンツから新しいコンテンツに変更する責任があります。ただし、コンパイラではstd :: string :: operator =()をスキップできないため、一時オブジェクトを生成する必要があります。しかし、s3が古いコンテンツのない新しいオブジェクトである場合はどうなりますか?この場合、古いコンテンツについて心配する必要はありません。現時点では、コンパイラはストレージに一時オブジェクトの代わりにs3を使用できます。s1 + s2の結果は直接コピーされ、s3オブジェクトに構築されます。s3は、不要になった一時オブジェクトを置き換えます
主なポイントのまとめ
一時オブジェクトは、コンストラクタとデストラクタの形でパフォーマンスを半分に低下させます。
コンストラクターを明示的に宣言すると、バックグラウンドで型変換を使用するようにコンパイラーを編成できます。
コンパイラーは、型の不一致の問題を解決するために一時オブジェクトを作成することがよくあります。これは、関数のオーバーロードによって回避できます。
可能であれば、オブジェクトのコピーは可能な限り回避する必要があります(関数の戻り値は値で返され、関数の引数は値で渡されます)。参照によるオブジェクトの受け渡しと返却
演算子は「+、-、*」または「/」の場合があります。operator =演算子を使用して、一時オブジェクトを削除します
シングルスレッドメモリプール
割り当てサイズの観点からの分類
固定サイズの
メモリを割り当てる固定サイズのメモリマネージャ任意のサイズのメモリブロックを割り当てる
可変サイズのメモリマネージャ
。要求された割り当てのサイズは不明です。
並行性の観点から検討する
シングルスレッド
メモリマネージャはシングルスレッドに制限されています。メモリはスレッドによって使用され、スレッドの制限を超えません。この種のメモリマネージャには、相互アクセスを伴う複数のスレッドはありません。
マルチスレッド
メモリマネージャは、複数のスレッドによって同時に使用されます。この実装には、相互に排他的に実行されるコードセグメントを含める必要があります。いつでも、1つのスレッドだけがコードセグメントを実行できます。