Kullanışlı C ++ yapı başlatma


141

'Pod' C ++ yapıları başlatmak için uygun bir yol bulmaya çalışıyorum. Şimdi, aşağıdaki yapıyı düşünün:

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

Bunu rahatça C (!) İle başlatmak istiyorsanız, sadece şunu yazabilirim:

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

Aşağıdaki gösterimlerden açıkça kaçınmak istediğimi unutmayın, çünkü gelecekte yapıdaki herhangi bir şeyi değiştirirsem boynumu kırmam için bana çarpıyor:

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

/* A */Örnekte olduğu gibi C ++ ile aynı (veya en azından benzer) ulaşmak için , bir aptal yapıcı uygulamak gerekir:

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

Hangi kaynar su için iyidir, ancak tembel insanlar için uygun değildir (tembellik iyi bir şey, değil mi?). Ayrıca, /* B */hangi değerin hangi üyeye gittiğini açıkça belirtmediği için , örnek kadar kötüdür .

Yani, sorum temelde benzer bir şeyi nasıl başarabileceğim /* A */ C ++ veya daha iyi bir ? Alternatif olarak, bunu neden yapmak istememem gerektiğine dair bir açıklama ile iyi olurum (yani zihinsel paradigmamın neden kötü olduğu).

DÜZENLE

By kullanışlı , ben de demek sürdürülebilir ve yedek olmayan .


2
Bence B örneği alacağınız kadar yakın.
Marlon

2
B örneğinin nasıl "kötü stil" olduğunu anlamıyorum. Her üyeyi kendi değerleriyle sırayla başlattığınızdan, bu bana mantıklı geliyor.
Mike Bailey

26
Mike, bu kötü bir stil çünkü hangi değerin hangi üyeye gittiği belli değil. Yapının tanımına bakmalı ve her değerin ne anlama geldiğini bulmak için üyeleri saymalısınız.
jnnnnn

9
Ayrıca, FooBar'ın tanımı gelecekte değişecek olsaydı, başlatma bozulabilir.
Edward Falk

başlatma uzun ve karmaşık hale gelirse, oluşturucu desenini unutmayın
kızak

Yanıtlar:


20

Belirtilen başlatmalar c ++ 2a'da desteklenecektir, ancak beklemek zorunda değilsiniz, çünkü bunlar GCC, Clang ve MSVC tarafından resmen desteklenmektedir .

#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 Demo MSVC Demo


Uyarı emptor: Yapının sonuna daha sonra parametreler eklerseniz, eski başlatmaların başlatılmadan hala sessizce derleneceğini unutmayın.
Catskul

1
@Catskul sayılı Bu başlatılır sıfır başlatma içine sonuçlanır boş başlatıcı listesi ile.
ivaigult

Haklısın. Teşekkür ederim. Açıklığa kavuşturmalıyım, kalan parametreler sessizce etkin bir şekilde varsayılan olarak başlatılacaktır. Demek istediğim, bunun umuduyla herkesin POD türlerinin tamamen açık bir şekilde başlatılmasına yardımcı olabileceğini hayal kırıklığına uğratacaktı.
Catskul

43

Yana style AC izin verilmez ++ ve istemediğiniz style Bnasıl kullanma hakkında daha sonra style BX:

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

En azından bir dereceye kadar yardım et.


8
+1: doğru başlatmayı gerçekten sağlamaz (derleyici POV'den) ama emin olun okuyucuya yardımcı olur ... yorumların senkronize olması gerekir.
Matthieu M.

18
Yorum Ben arasında yeni bir alan eklerseniz kesilmesini yapının başlatma engellemez foove bargelecekte. C hala istediğimiz alanları başlatır, ancak C ++ başlamaz. Ve bu sorunun noktası - C ++ 'da aynı sonuca nasıl ulaşılır. Yani, Python bunu isimlendirilmiş argümanlarla, C - "adlandırılmış" alanlarla yapıyor ve C ++ 'da da bir şey olmalı, umarım.
dmitry_romanov

2
Yorumlar senkronize mi? Bana bir ara ver. Güvenlik pencereden geçer. Parametreleri ve bomu yeniden sıralayın. İle çok daha iyi explicit FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) . Not açık anahtar sözcüğü. Standardı kırmak bile güvenlik açısından daha iyidir. Clang'da: -Wno-c99-uzantıları
Daniel O

@DanielW, Neyin daha iyi ya da neyin olmadığı ile ilgili değil. bu yanıt OP'nin geçerli tüm durumları kapsayan Stil A (c ++ değil), B veya C istemediğine göre.
iammilind

@iammilind Bence OP'nin zihinsel paradigmasının neden kötü olduğuna dair bir ipucu cevabı geliştirebilir. Bunu şu anda olduğu gibi tehlikeli buluyorum.
Daerst


9

Sabitleri, onları tanımlayan fonksiyonlara çıkarın (temel yeniden düzenleme):

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

Stilin kullanmak istemediğinize çok yakın olduğunu biliyorum, ancak sabit değerlerin daha kolay değiştirilmesini sağlar ve aynı zamanda bunları açıklar (böylece yorumları düzenlemeye gerek yoktur).

Yapabileceğiniz başka bir şey (tembel olduğunuz için) yapıcıyı satır içi yapmaktır, bu yüzden çok fazla yazmak zorunda kalmazsınız ("Foobar ::" ve h ve cpp dosyası arasında geçiş yapmak için harcanan zaman):

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

1
Yapmanız gereken tek şey bir dizi değerle yapıları hızlı bir şekilde başlatabiliyorsa, bu soruyu okuyan herkese bu cevap için alt kod snippet'indeki stili seçmesini şiddetle tavsiye ederim.
kayleeFrye_onDeck

8

Sorunuz biraz zor çünkü işlev bile:

static FooBar MakeFooBar(int foo, float bar);

şu şekilde adlandırılabilir:

FooBar fb = MakeFooBar(3.4, 5);

yerleşik sayısal türler için tanıtım ve dönüşüm kuralları nedeniyle. (C hiçbir zaman gerçekten güçlü bir şekilde yazılmamıştır)

C ++ 'da, şablonlar ve statik iddialar yardımıyla istediğiniz şey elde edilebilir:

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'de parametreleri adlandırabilirsiniz, ancak daha fazla ilerleyemezsiniz.

Öte yandan, tüm istediğiniz parametreler olarak adlandırılırsa, çok hantal bir kod yazarsınız:

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);

İsterseniz tip tanıtım korumasında biber yapabilirsiniz.


1
"C ++ ile istediğiniz şey elde edilebilir": OP parametre sırasının karışmasını önlemeye yardımcı olmasını istemiyor muydu? Teklif ettiğiniz şablon bunu nasıl başaracak? Sadece basitlik için, diyelim ki ikisi de int.
fazla

@max: OP türleri, sadece türler farklıysa (birbirlerine dönüştürülebilir olsalar bile) engeller. Türleri ayırt edemezse, elbette işe yaramaz, ancak bu başka bir soru.
Matthieu M.13

Ah anladım. Evet, bunlar iki farklı problem ve sanırım ikincisinin şu anda C ++ 'da iyi bir çözümü yok (ancak C ++ 20, toplam başlatmadaki C99 stili parametre adları için destek ekliyor gibi görünüyor).
en fazla

6

Birçok derleyicinin C ++ ön uçları (GCC ve clang dahil) C başlatıcı sözdizimini anlar. Yapabiliyorsanız, bu yöntemi kullanın.


16
Hangi C ++ standardına uygun değil!
bit maskesi

5
Standart olmadığını biliyorum. Ancak bunu kullanabiliyorsanız, bir yapıyı başlatmanın en mantıklı yolu budur.
Matthias Urlichs

2
Yanlış yapıcı özel yapma x ve y türlerini koruyabilirsiniz:private: FooBar(float x, int y) {};
dmitry_romanov

4
clang (llvm tabanlı c ++ derleyicisi) de bu sözdizimini destekler. Çok kötü, standardın bir parçası değil.
nimrodm

Hepimiz biliyoruz ki C başlatıcıları C ++ standardının bir parçası değildir. Ancak birçok derleyici bunu anlıyor ve soru, eğer varsa, hangi derleyicinin hedeflendiğini söylemedi. Bu yüzden lütfen bu cevabı küçümsemeyin.
Matthias Urlichs

4

C ++ 'da başka bir yol

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
Fonksiyonel programlama için hantal (yani bir fonksiyon çağrısının argüman listesinde nesneyi oluşturmak), ama aksi takdirde gerçekten temiz bir fikir!
bitmask

27
optimizer muhtemelen azaltıyor, ama gözlerim azalmıyor.
Matthieu M.

6
İki kelime: argh ... argh! Bu, 'Point pt; pt.x = pt.y = 20; `? Ya da kapsülleme yapmak istiyorsanız, bu bir kurucudan nasıl daha iyidir?
OldPeculier

3
Bir kurucudan daha iyidir çünkü parametre siparişi için kurucu beyanına bakmanız gerekir ... x, y veya y, x, ancak bunu gösterme
biçimim

2
Bir const yapı istiyorsanız bu çalışmaz. veya derleyiciye başlatılmamış yapılara izin vermemesini söylemek istiyorsanız. Eğer varsa gerçekten bu şekilde yapmak istiyorum, en azından ile ayarlayıcılar işaretlemek inline!
Matthias Urlichs

3

Seçenek D:

FooBar FooBarMake(int foo, float bar)

Yasal C, yasal C ++. POD'lar için kolayca optimize edilebilir. Elbette adlandırılmış bir argüman yok, ama bu tüm C ++ gibi. Adlandırılmış bağımsız değişkenler istiyorsanız, Amaç C daha iyi bir seçim olmalıdır.

E Seçeneği:

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

Yasal C, yasal C ++. Adlandırılmış argümanlar.


12
FooBar fb = {};C ++ içinde kullanabileceğiniz memset yerine, varsayılan olarak tüm yapı üyelerini başlatır.
Öö Tiib

@ ÖöTiib: Ne yazık ki bu yasadışı C.
CB Bailey

3

Bu sorunun eski olduğunu biliyorum, ancak C ++ 20 sonunda bu özelliği C ++ C ++ 'a getirene kadar bunu çözmenin bir yolu var. Bunu çözmek için yapabileceğiniz şey, başlatmanızın geçerli olup olmadığını kontrol etmek için static_asserts ile önişlemci makroları kullanmaktır. (Makroların genellikle kötü olduğunu biliyorum, ama burada başka bir yol göremiyorum.) Aşağıdaki örnek koda bakın:

#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...

Sonra const özniteliklerine sahip bir yapınız varsa, bunu yapabilirsiniz:

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.);

Biraz sakıncalıdır, çünkü olası her sayıdaki özellik için makrolara ve makro çağrısında örneğinizin türünü ve adını tekrarlamanız gerekir. Ayrıca makrolar bir return deyiminde kullanılamaz, çünkü varsayımlar başlatma işleminden sonra gelir.

Ancak sorununuzu çözer: Yapıyı değiştirdiğinizde çağrı derleme zamanında başarısız olur.

C ++ 17 kullanıyorsanız, aynı türleri zorlayarak bu makroları daha katı hale getirebilirsiniz, örneğin:

#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);\

Adı belirtilen başlatıcılara izin vermek için bir C ++ 20 önerisi var mı?
Maël Nison


2

/* B */C ++ 'da yol gayet iyi C ++ 0x sözdizimini genişletecek ve C ++ kapsayıcıları için de faydalı olacak. Neden kötü stil dediğini anlamıyorum?

Parametreleri adlarla belirtmek istiyorsanız boost parametre kitaplığını kullanabilirsiniz , ancak tanıdık olmayan birisini karıştırabilir.

Yapı üyelerini yeniden sıralamak işlev parametrelerini yeniden sıralamaya benzer, bu tür yeniden düzenleme çok dikkatli yapmazsanız sorunlara neden olabilir.


7
Ben buna kötü stil diyorum çünkü sıfır bakım yapılabilir. Yılda başka bir üye eklersem ne olur? Veya üyelerin sırasını / türlerini değiştirirsem? Başlatan her kod parçası (büyük olasılıkla) kırılabilir.
bitmask

2
@bitmask Ancak bağımsız değişkenler adlandırmadığınız sürece, yapıcı çağrılarını da güncellemeniz gerekir ve bence pek çok kişi yapıcıların sürdürülemez kötü stil olduğunu düşünmez. Ayrıca adlandırılan başlatma C değil, C ++ kesinlikle bir üst küme değildir C99 olduğunu düşünüyorum.
Christian Rau

2
Yapının sonuna bir yıl içinde başka bir üye eklerseniz, mevcut kodda varsayılan olarak başlatılır. Bunları yeniden sıralarsanız, mevcut tüm kodu düzenlemeniz gerekir, yapılacak bir şey yoktur.
Öö Tiib

1
@bitmask: O zaman ilk örnek "sürdürülemez" olacaktır. Bunun yerine yapıdaki bir değişkeni yeniden adlandırırsanız ne olur? Elbette, bir yenisini değiştirebilirsiniz, ancak bu, yeniden adlandırılmaması gereken bir değişkeni yanlışlıkla yeniden adlandırabilir.
Mike Bailey

@ChristianRau C99 ne zamandan beri C değil? C grubu ve C99 belirli bir sürüm / ISO spesifikasyonu değil mi?
altendky

1

Bu sözdizimine ne oldu?

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

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

Sadece bir Microsoft Visual C ++ 2015 ve g ++ 6.0.2 üzerinde test edildi. Tamam çalışıyor.
Değişken adının kopyalanmasını önlemek istiyorsanız, belirli bir makro da yapabilirsiniz.


clang++3.5.0-10 ile -Weverything -std=c++1zdoğruladığı görülüyor. Ama doğru görünmüyor. Standardın bunun geçerli C ++ olduğunu doğruladığını biliyor musunuz?
Bitmask

Bilmiyorum, ama bunu uzun zamandan beri farklı derleyicilerde kullandım ve herhangi bir sorun görmedim. Şimdi g ++ 4.4.7 üzerinde test edildi - iyi çalışıyor.
cls

5
Bu işi sanmıyorum. Deneyin ABCD abc = { abc.b = 7, abc.a = 5 };.
raymai97

@deselect, alan, işleç = tarafından döndürülen değerle başlatıldığı için çalışır. Yani, aslında sınıf üyesini iki kez başlatırsınız.
Dmytro Ovdiienko

1

Benim için satır içi inizializasyona izin vermenin en tembel yolu bu makroyu kullanmaktır.

#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);

Bu makro öznitelik ve kendi kendine başvuru yöntemi oluşturur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.