C ++のネストされたプライベート構造体タイプを考えると、ファイルスコープの静的関数からそれにアクセスするための戦術はありますか?

害虫

誰かが次の引用で著者ジョン・ラコスによってほのめかされた正確なコーディング戦術を説​​明できますか?

ジョン・ラコス:

さらに議論の余地があるのは、構造体のコピーを2つ持つ方がよい場合が多いことです。たとえば、1つは.hファイルにネスト/プライベート(インラインメソッドやフレンドにアクセス可能)、もう1つは.cppファイルのファイルスコープに(file-にアクセス可能)です。スコープ静的関数)—実装の詳細でグローバル空間を汚染するのではなく、ローカルで同期を維持するようにしてください。

引用は、新しいLakosの本であるLarge Scale C ++に表示されます。

(この本は、Lakosの以前の本であるLarge Scale C ++ Software Designと同じ主題の更新された扱いです

引用はセクション0.2、9ページにあります。

後の章でLakosが何を説明しているのかが明確になったら、ここに戻って回答を投稿します。

その間、私は引用を理解することに魅了されました、そして、さらなる手がかりのために本の目次と索引をスキャンする私の試みはまだ答えをもたらしませんでした。

これは、神秘的な戦術によって解決されると私が想像する問題の私自身のサンプルコードです

// THE HEADER

namespace project
{
class OuterComponent
{
public:
    inline int GetFoo()
    {
        return m_inner.foo;
    }

    int GetBar();

private:
    struct InnerComponent
    {
        int foo = 0;
        int bar = 0;
    };

    InnerComponent m_inner;
};
} // namespace project

一緒に:

// THE CPP IMPLEMENTATION FILE

namespace project
{
namespace
{
    /*
       MYSTERY:
       Per the book quotation, I can somehow add a "copy of InnerComponent" here?
       And "make sure to keep them in sync locally"?
    */

    // COMPILATION ERROR (see below)
    int FileScopeComputation( OuterComponent::InnerComponent i )
    {
        return i.bar - 3;
    }
} // namespace

int OuterComponent::GetBar()
{
    return FileScopeComputation( m_inner );
}

} // namespace project

もちろん、上記はコンパイルされません。

エラーは次のようになります。

error: ‘struct project::OuterComponent::InnerComponent’ is private within this context
     int FileScopeComputation( OuterComponent::InnerComponent i )
                                               ^~~~~~~~~~~~~~

名前の付いた無料の関数は、私がよく理解している理由でFileScopeComputationアクセスできませんInnerComponent

上記のコードを本の見積もりに関連付ける

これをLakosの引用に戻すと、私の考えはFileScopeComputation、引用が「ファイルスコープ静的関数」と呼んでいるものの1つのインスタンスである考えています

コードをコンパイルするInnerComponentための「明らかな」解決策の1つは、のpublicセクションで宣言されるように移動することですOuterComponent

しかし、私は自分の「明白な」解決策を(引用によると)「実装の詳細でグローバル空間を[汚染]する」罪を犯していると考えています。

したがって、私のコードは、(a)ほのめかされた戦術の目標と、(b)1つの潜在的な解決策の望ましくない汚染の両方を捉えているようです。では、代替ソリューションは何ですか?

どちらかまたは両方に答えてください:

(1)struct InnerComponentフィールドOuterComponent::m_innerがプライベートのままで、タイプOuterComponent::InnerComponentプライベートのままであるように、cppファイルに別のコピーを作成する方法はありますFileScopeComputationか?それでも、関数には、のインスタンス上のデータにアクセスするのと「同等の」何かを行う方法があります。InnerComponent

私はキャスティングでいくつかの奇妙なことを試しましたが、本の中で推薦する価値があるように見えるものは何もありません。一方、本の中でラコスが推奨するものはすべて、非常に推奨に値するものであるというのが私の経験です。

(2)見積もりがどのようなシナリオに適用されるかを完全に誤解していますか?もしそうなら、引用が実際に言及しているC ++ソフトウェア設計の問題は何ですか?他にどのような問題がありますか?「構造体の2つのコピー... 1つはhファイルにあります...もう1つはcppファイルにあります」

更新:

dfriによる知覚的な答えに基づいて、上記のコードは実際に最小限の変更でコンパイルすることができ、次のようになります。

  • フィールドOuterComponent::m_innerはプライベートのままです
  • タイプOuterComponent::InnerComponentもプライベートのままです
  • それでも、この関数にFileScopeComputationは、次のインスタンスのデータにアクセスする方法があります。InnerComponent

ヘッダーコードには、次の1行のコードが追加されます。

...
private:
    struct InnerUtil; // <-- this line was ADDED. all else is same as above.
    struct InnerComponent
    {
        int foo = 0;
        int bar = 0;
    };

    InnerComponent m_inner;
};

そして、cppファイルコードは次のようになります。

struct OuterComponent::InnerUtil
{
    static int FileScopeComputation( OuterComponent::InnerComponent i )
    {
        return i.bar - 3;
    }
};

int OuterComponent::GetBar()
{
    return InnerUtil::FileScopeComputation( m_inner );
}

dfrib

因数分解パターン(Lakos)

Lakosが実際には、パブリックAPIデリゲートが呼び出す別の名前の型を参照している可能性があります。引用とLakosFactoringパターン(「ImpおよびImpUtilパターン」)、特に「ImpUtil」の部分との間に類似感があります。

struct A {};
struct B {};
struct C {};

// widgetutil.h
// (definitions placed in widgetutil.cpp)
struct WidgetUtil {
    // "Keep API in sync with Widget::foo".
    static void foo(const A& b, const B& c, const C& a) {
        // All implementation here in the util.
        (void)a; (void)b; (void)c;
    }
    
    // "Keep API in sync with Widget::bar".
    static void bar(const B& b, const C& c) {
        // All implementation here in the util.
        (void)b; (void)c;
    }
};

// widget.h
// includes "widgetutil.h"

// Public-facing API
// (Ignoring the Imp pattern, only using the Util pattern).
struct Widget {
    void foo(const A& a, const B& b) const {
        // Only delegation to the util.
        WidgetUtil::foo(a, b, c_);
    }

    void bar(const B& b) const {
        // Only delegation to the util.
        WidgetUtil::bar(b, c_);
    }

private:
    C c_{};
};

int main() {
    const Widget w;
    w.foo(A{}, B{});  // --> WidgetUtil::foo
}

これは、実装の詳細(WidgetUtil)を公開API(Widgetから分離するための1つのアプローチであり、テストも容易にします。

  • のユニットテストで孤立してテストした実装の詳細ユニットWidgetUtil
  • のテストはWidgetWidgetUtilメンバーの副作用を心配することなく実行できます。メンバーは、Widget(クラステンプレートにする)静的依存性注入を使用して完全にモックすることができるためです。

Lakos(OP内)からの引用を振り返ると、パブリックAPIから隠されたWidgetUtilwidget.cppソースファイル内のファイルスコープクラスとして配置することもできます。これは、さらに多くのカプセル化を意味しますが、上記の分離と同じようにテストを容易にすることはできません。

最後に、Widgetクラステンプレートを作成することは、必ずしもwidget.h定義を使用してのユーザーの翻訳単位を汚染することを意味するわけではないことに注意してください定義を含む各TUでコンパイルしてインスタンス化する必要がありますWidget)。Widgetテストを容易にするためだけクラステンプレートとして作成れるため、その製品実装では、単一のインスタンス化、つまり製品の注入のみが使用されますWidgetUtilこれはWidget、非テンプレートクラスの場合と同様に、クラステンプレートメンバー関数の定義を宣言から分離し、Widget<WidgetUtil>特殊化を明示的にインスタンス化できることを意味しますwidget.cppたとえば、次のアプローチを使用します。

  • クラステンプレートのメンバー関数のWidget定義を、クラステンプレート定義の-timpl.hヘッダーファイルから別のヘッダーファイルに分離します。
  • 次に、-timpl.hヘッダーファイルは関連.cppファイルに含まれます。このファイルにWidgetは、本番環境で使用される単一タイプのテンプレート引数のクラステンプレートの明示的なインスタンス化が含まれますWidgetUtil

同様に、テストには-timpl.hヘッダーをインクルードWidgetし、代わりにモックされたutilクラスを使用しクラステンプレートをインスタンス化できます

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ