web-dev-qa-db-ja.com

便利なC ++構造体の初期化

「ポッド」C++構造体を初期化する便利な方法を見つけようとしています。ここで、次の構造体を検討してください。

struct FooBar {
  int foo;
  float bar;
};
// just to make all examples work in C and C++:
typedef struct FooBar FooBar;

C(!)でこれを便利に初期化したい場合、次のように書くことができます。

/* A */ FooBar fb = { .foo = 12, .bar = 3.4 }; // illegal C++, legal C

将来構造体でanythingを変更すると首を折るように作られているため、次の表記を明示的に避けたいことに注意してください:

/* B */ FooBar fb = { 12, 3.4 }; // legal C++, legal C, bad style?

/* A */の例と同じ(または少なくとも同様の)C++を実現するには、ばかげたコンストラクターを実装する必要があります。

FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) {}
// ->
/* C */ FooBar fb(12, 3.4);

これはお湯を沸かすのに適していますが、怠け者には適していません(怠lazは良いことですよね?)。また、/* B */の例と同じくらい悪いのは、どの値がどのメンバーに行くのかを明示していないためです。

それで、私の質問は基本的に、C++で/* A */またはそれ以上に似た何かを達成する方法ですか?あるいは、これをしたくない理由(つまり、私の精神的パラダイムが悪い理由)の説明で大丈夫でしょう。

編集

convenientとは、maintainable非冗長

136
bitmask

指定された初期化はc ++ 2aでサポートされますが、GCC、Clang、およびMSVCによって 公式にサポートされている であるため、待つ必要はありません。

#include <iostream>
#include <filesystem>

struct hello_world {
    const char* hello;
    const char* world;
};

int main () 
{
    hello_world hw = {
        .hello = "hello, ",
        .world = "world!"
    };

    std::cout << hw.hello << hw.world << std::endl;
    return 0;
}

GCC DemoMSVC Demo

14
ivaigult

style AはC++では許可されておらず、style Bが必要ないため、style BXを使用する方法は次のとおりです。

FooBar fb = { /*.foo=*/ 12, /*.bar=*/ 3.4 };  // :)

少なくともある程度は助けてください。

38
iammilind

ラムダを使用できます:

const FooBar fb = [&] {
    FooBar fb;
    fb.foo = 12;
    fb.bar = 3.4;
    return fb;
}();

このイディオムの詳細については、 Herb Sutterのブログ を参照してください。

8
eyelash

あなたの質問はやや難しいです

static FooBar MakeFooBar(int foo, float bar);

次のように呼び出すことができます。

FooBar fb = MakeFooBar(3.4, 5);

組み込みの数値型のプロモーションと変換のルールのためです。 (Cは本当に強く型付けされたことがない)

C++では、テンプレートと静的アサーションの助けを借りて、必要なものを実現できます。

template <typename Integer, typename Real>
FooBar MakeFooBar(Integer foo, Real bar) {
  static_assert(std::is_same<Integer, int>::value, "foo should be of type int");
  static_assert(std::is_same<Real, float>::value, "bar should be of type float");
  return { foo, bar };
}

Cでは、パラメーターに名前を付けることができますが、それ以上取得することはありません。

一方、必要なのが名前付きパラメーターだけである場合、多くの面倒なコードを作成します。

struct FooBarMaker {
  FooBarMaker(int f): _f(f) {}
  FooBar Bar(float b) const { return FooBar(_f, b); }
  int _f;
};

static FooBarMaker Foo(int f) { return FooBarMaker(f); }

// Usage
FooBar fb = Foo(5).Bar(3.4);

必要に応じて、タイププロモーションの保護を追加できます。

8
Matthieu M.

定数を記述する関数に定数を抽出します(基本的なリファクタリング):

FooBar fb = { foo(), bar() };

スタイルはあなたが使いたくないものに非常に近いことを知っていますが、定数値を簡単に置き換えて説明することができます(したがって、コメントを編集する必要はありません)。

(怠け者だから)できるもう1つのことは、コンストラクターをインラインにすることです。そのため、あまり入力する必要はありません(「Foobar ::」を削除し、hファイルとcppファイルの切り替えに費やした時間):

struct FooBar {
  FooBar(int f, float b) : foo(f), bar(b) {}
  int foo;
  float bar;
};
7
ralphtheninja

多くのコンパイラのC++フロントエンド(GCCおよびclangを含む)は、C初期化構文を理解します。可能であれば、その方法を使用してください。

5

この質問は古いことは知っていますが、C++ 20がこの機能をCからC++にもたらすまで、これを解決する方法があります。これを解決するには、プリプロセッサマクロをstatic_assertsと共に使用して、初期化が有効であることを確認します。 (マクロは一般的に悪いことは知っていますが、ここでは別の方法は見当たりません。)以下のサンプルコードを参照してください。

#define INVALID_STRUCT_ERROR "Instantiation of struct failed: Type, order or number of attributes is wrong."

#define CREATE_STRUCT_1(type, identifier, m_1, p_1) \
{ p_1 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_2(type, identifier, m_1, p_1, m_2, p_2) \
{ p_1, p_2 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_4(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3, m_4, p_4) \
{ p_1, p_2, p_3, p_4 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_4) >= (offsetof(type, m_3) + sizeof(identifier.m_3)), INVALID_STRUCT_ERROR);\

// Create more macros for structs with more attributes...

次に、const属性を持つ構造体がある場合、これを実行できます。

struct MyStruct
{
    const int attr1;
    const float attr2;
    const double attr3;
};

const MyStruct test = CREATE_STRUCT_3(MyStruct, test, attr1, 1, attr2, 2.f, attr3, 3.);

可能な限り多くの属性に対してマクロが必要であり、マクロ呼び出しでインスタンスのタイプと名前を繰り返す必要があるため、少し不便です。また、アサートは初期化後に行われるため、returnステートメントでマクロを使用することはできません。

ただし、問題は解決します。構造体を変更すると、コンパイル時に呼び出しが失敗します。

C++ 17を使用している場合は、同じタイプを強制することで、これらのマクロをより厳密にすることもできます。例:

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_1) == typeid(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_2) == typeid(identifier.m_2), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_3) == typeid(identifier.m_3), INVALID_STRUCT_ERROR);\
2
Max Vollmer

C++のさらに別の方法は

struct Point
{
private:

 int x;
 int y;

public:
    Point& setX(int xIn) { x = Xin; return *this;}
    Point& setY(int yIn) { y = Yin; return *this;}

}

Point pt;
pt.setX(20).setY(20);
2

オプションD:

FooBar FooBarMake(int foo, float bar)

リーガルC、リーガルC++。 PODに簡単に最適化できます。もちろん、名前付き引数はありませんが、これはすべてのC++に似ています。名前付き引数が必要な場合は、Objective Cをお勧めします。

オプションE:

FooBar fb;
memset(&fb, 0, sizeof(FooBar));
fb.foo = 4;
fb.bar = 15.5f;

リーガルC、リーガルC++。名前付き引数。

2
John

/* B */はC++でも問題ありませんが、C++ 0xは構文を拡張するため、C++コンテナにも役立ちます。なぜあなたはそれを悪いスタイルと呼ぶのか分かりませんか?

パラメータを名前で示したい場合、 boost parameter library を使用できますが、それを知らない人を混乱させる可能性があります。

構造体メンバーの並べ替えは、関数パラメーターの並べ替えに似ています。このようなリファクタリングは、慎重に行わないと問題を引き起こす可能性があります。

1
Öö Tiib

この構文はどうですか?

typedef struct
{
    int a;
    short b;
}
ABCD;

ABCD abc = { abc.a = 5, abc.b = 7 };

Microsoft Visual C++ 2015およびg ++ 6.0.2でテスト済みです。 OK。
変数名の重複を避けたい場合にも、特定のマクロを作成できます。

0
deselect

私にとって、インライン初期化を許可する最も遅延な方法は、このマクロを使用することです。

#define METHOD_MEMBER(TYPE, NAME, CLASS) \
CLASS &set_ ## NAME(const TYPE &_val) { NAME = _val; return *this; } \
TYPE NAME;

struct foo {
    METHOD_MEMBER(string, attr1, foo)
    METHOD_MEMBER(int, attr2, foo)
    METHOD_MEMBER(double, attr3, foo)
};

// inline usage
foo test = foo().set_attr1("hi").set_attr2(22).set_attr3(3.14);

そのマクロは、属性と自己参照メソッドを作成します。

0