2

BruceEckelによる「ThinkinginC++」の助けを借りて、C ++を学習し、演習32、第10章で立ち往生しています。問題は、オブジェクトm5に対して呼び出されたMirror :: test()がfalseを返すリンク順序を変更する方法です。これが私のコードです。

mirror.h:

#ifndef MIRROR_H_
#define MIRROR_H_

class Mirror {
 public:
  Mirror() {logic_ = true; self_ = 0;};
  Mirror(Mirror *ptr) {self_ = ptr; logic_ = false;};
  bool test() {
    if (self_ != 0) {
      return self_->test();
    } else {
      return logic_;
    }
  };

 private:
  bool logic_;
  Mirror *self_;
};


#endif // MIRROR_H_

タスク

one.cpp

#include "mirror.h"
Mirror m1;

two.cpp

#include "mirror.h"
extern Mirror m1;
Mirror m2 (&m1);

three.cpp

#include "mirror.h"
extern Mirror m2;
Mirror m3 (&m2);

等々。ついに、

five.cpp

#include "mirror.h"

#include <iostream>

extern Mirror m4;
Mirror m5 (&m4);

int main(int argc, char* argv[]) {
  std::cout << m5.test() << std::endl;
}

m5.test()はtrueを返します。タスクは、リンクの順序を変更する必要があることを示しています。m5.test()はfalseを返します。私は使用しようとしました:

init_priority(priority)

標準C++では、名前空間スコープで定義されたオブジェクトは、特定の変換単位での定義と厳密に一致する順序で初期化されることが保証されています。翻訳ユニット間の初期化は保証されません。ただし、GNU C ++を使用すると、ユーザーは、相対優先度(現在、101から65535までの範囲にある定数積分式)を指定することにより、init_priority属性を使用して名前空間スコープで定義されたオブジェクトの初期化の順序を制御できます。数値が小さいほど、優先度が高くなります。

しかし、運はありません。

完全な演習テキスト:

ヘッダーファイルで、2つのデータメンバー(Mirrorオブジェクトへのポインターとbool)を含むクラスMirrorを作成します。2つのコンストラクターを指定します。デフォルトのコンストラクターはboolをtrueに初期化し、Mirrorポインターをゼロに初期化します。2番目のコンストラクターは、引数としてMirrorオブジェクトへのポインターを取り、それをオブジェクトの内部ポインターに割り当てます。boolをfalseに設定します。メンバー関数test()を追加します。オブジェクトのポインターがゼロ以外の場合、ポインターを介して呼び出されたtest()の値を返します。ポインタがゼロの場合、ブール値を返します。次に、5つのcppファイルを作成します。各ファイルには、ミラーヘッダーが含まれています。最初のcppファイルは、デフォルトのコンストラクターを使用してグローバルミラーオブジェクトを定義します。2番目のファイルは、最初のファイルのオブジェクトをexternとして宣言し、2番目のコンストラクターを使用してグローバルミラーオブジェクトを定義します。最初のオブジェクトへのポインタを使用します。グローバルオブジェクト定義も含まれる最後のファイルに到達するまで、これを繰り返します。そのファイルで、main()はtest()関数を呼び出し、結果を報告する必要があります。結果がtrueの場合は、リンカーのリンク順序を変更して、結果がfalseになるまで変更する方法を確認してください。

4

2 に答える 2

5

オブジェクトファイルをリンカに渡すときは、オブジェクトファイルの順序を変更する必要があります。これはトップレベルのコードには適切に機能しますが、コンパイラが異なれば使用するアプローチも異なります。つまり、移植性はありません。また、ライブラリの場合、通常、オブジェクトが含まれる順序を制御することはできません。たとえば、

// file1.cpp
int main() {
}

// file2.cpp
#include <iostream>
static bool value = std::cout << "file2.cpp\n";

// file3.cpp
#include <iostream>
static bool value = std::cout << "file3.cpp\n";

...そしてあなたはこのような2つのプログラムをリンクします:

g++ -o tst1 file1.cpp file2.cpp file3.cpp
g++ -o tst2 file1.cpp file3.cpp file2.cpp

tst1とに対して異なる出力が得られますtst2。例:

$ ./tst1
file2.cpp
file3.cpp
$ ./tst2
file3.cpp
file2.cpp

全体的な道徳は次のとおりです。それをしないでください。つまり、グローバルオブジェクトを使用しないでください。どうしてもグローバルオブジェクトを使用する必要があると思われる場合は、それらを関数にカプセル化します。例:

Type& global_value() {
    static Type value; // possibly with constructor arguments
    return value;
}

このようにvalue、最初にアクセスされたときに初期化され、まだ構築されていない間はアクセスする方法がありません。このようにすべてのオブジェクトをカプセル化すると、適切な順序で構築されることを保証できます(循環依存がある場合を除き、その場合は機能させることができず、設計を真剣に再考する必要があります)。オブジェクトを関数にカプセル化する上記のアプローチは、残念ながら、C ++ 2003ではスレッドセーフではありません。ただし、C++2011ではスレッドセーフです。それでも、グローバル変数の使用は一般的に問題があり、それらの使用を最小限に抑える必要があります。

于 2012-09-02T13:47:27.567 に答える
1

私もこの運動に苦労していました。

オブジェクトファイルのすべての可能な順列を使用して最終的な実行可能ファイルをリンクおよびテストするmakefileエントリを準備するために、小さなPythonスクリプトを作成することができました。

import itertools

for perm in itertools.permutations([1, 2, 3, 4, 5]):
    print '\tg++ u0{0}.o u0{1}.o u0{2}.o u0{3}.o u0{4}.o -o $@ && ./main.exe'.format(*perm)

私のmakeプロセスを実行した後、可能なすべての構成がtrue価値を生み出したことがわかりました。

これは、すべてのグローバル(つまり静的)変数がmain関数に入る前に初期化されることが保証されているという事実によるものです。

前の関数の結果を保持するグローバルbool変数を次のように定義しました。test()main

#include "mirror.h"

#include <iostream>

extern Mirror m4;
Mirror m5 (&m4);

bool result = m5.test();

int main(int argc, char* argv[]) {
  std::cout << result << std::endl;
}

ビンゴ!falseプログラムの出力で生成されたオブジェクトの順列の一部。

すべての静的変数は、可能なコンストラクターが呼び出される前にゼロで初期化されます。この演習では、コンストラクターが呼び出される順序が手がかりになります。

変数の値が確立されたときに、依存関係チェーン内のいずれかのオブジェクトがコンストラクターによって初期化されていない場合result、結果はfalsevalueになります(self_valueは0で、logic_valueはfalseなので、テスト関数はを返しますfalse)。

result関数に入る前に変数を評価する場合、mainそのような可能性があり、リンカコマンドでのオブジェクトファイルの順序は結果と関係があります。

于 2013-05-29T23:51:51.600 に答える