Geçicilere nasıl izin verilmez


107

Bir Foo sınıfı için, ona bir isim vermeden inşa etmeye izin vermemenin bir yolu var mı?

Örneğin:

Foo("hi");

Ve sadece aşağıdaki gibi bir isim verirseniz izin verir misiniz?

Foo my_foo("hi");

İlkinin kullanım ömrü sadece ifadedir ve ikincisi de çevreleyen bloktur. Benim kullanım durumumda, Fookurucu ve yıkıcı arasındaki süreyi ölçmek. Yerel değişkene asla başvurmadığım için, sık sık onu yerleştirmeyi unutuyorum ve yanlışlıkla yaşam süresini değiştiriyorum. Bunun yerine bir derleme zamanı hatası almak istiyorum.


8
Bu, muteks kilit korumaları için de kullanışlı olabilir.
lucas clemente

1
Eh, sen olabilir Yasak olmasına Kendi C ++ derleyicisi yazmak, ancak kesinlikle daha sonra ++ o C olmazdı konuşan. Bunun gibi geçicilerin yararlı olacağı yerler de vardır, örneğin bir işlevden bir nesneyi döndürürken olduğu gibi (örneğin return std::string("Foo");)
Bazı programcı dostum

2
Hayır, bunu yapamazsınız, üzgünüm
Armen Tsirunyan

2
Dininize bağlı olarak bu, makroların kullanışlı olabileceği bir durum olabilir (bu türü yalnızca her zaman
değişken

3
Bir derleyici saldırısı tarafından sözdizimsel olarak engellemek istediğim bir şeyden çok, LINT aracımın yakalamasını istediğim bir şeye benziyor.
Warren P

Yanıtlar:


101

Başka bir makro tabanlı çözüm:

#define Foo class Foo

Bildirimi Foo("hi");genişler class Foo("hi");kötü şekillendirilmiş olan; ama doğru olan olarak Foo a("hi")genişler class Foo a("hi").

Bu, mevcut (doğru) kodla hem kaynak hem de ikili uyumlu olma avantajına sahiptir. (Bu iddia tamamen doğru değil - lütfen Johannes Schaub'un Yorumuna ve aşağıdaki tartışmaya bakın: "Kaynak kodun mevcut kodla uyumlu olduğunu nasıl bilebilirsiniz? Arkadaşı başlığını içeriyor ve void f () {int Foo = 0;} daha önce iyi derleyen ve şimdi yanlış derleyen! Ayrıca, Foo sınıfının bir üye işlevini tanımlayan her satır başarısız olur: void class Foo :: bar () {} " )


51
Mevcut kodla kaynak uyumlu olduğunu nasıl bilebilirsiniz? Arkadaşı, void f() { int Foo = 0; }daha önce iyi derleyen ve şimdi yanlış derleyen başlığını içerir ! Ayrıca, sınıf Foo üyesi işlevini tanımlayan her satır başarısız: void class Foo::bar() {}.
Johannes Schaub -

21
Bu nasıl bu kadar çok oy alabilir? @ JohannesSchaub-litb tarafından yapılan yoruma bakın ve bunun gerçekten kötü bir çözüm olduğunu anlayacaksınız. Çünkü bundan sonra üye işlevlerin tüm tanımları geçersizdir .. -1 benim tarafımdan
Aamir

2
@JustMaximumPower: Umarım alaycıdır çünkü değilse, yine kötü (daha kötü okuyun) bir çözümdür. Çünkü onu tanımladıktan sonra tekrar başa döndük, bu da benzer bir satırda (OP'nin amaçladığı) bir derleme hatası almayacağınız anlamına gelir, yani Foo("Hi")şimdi Foo.cpp içinde
Aamir

1
@Aamir Hayır ciddiyim. Martin C. Martin, uygulamayı Foo'nun kullanımını korumak için kullanmayı amaçlıyor.
JustMaximumPower

1
Visual Studio 2012'de denedim ve class Foo("hi");derleme için uygun buldum .
12'de fresky

71

Biraz hacklemeye ne dersin

class Foo
{
    public:
        Foo (const char*) {}
};

void Foo (float);


int main ()
{
    Foo ("hello"); // error
    class Foo a("hi"); // OK
    return 1;
}

1
Harika Hack! Bir not: Foo a("hi");(olmadan class) da bir hata olur.
bit maskesi

Anladığımdan emin değilim. Foo ("merhaba"), void Foo (float) 'ı çağırmaya çalışır ve bir bağlayıcı hatasıyla sonuçlanır? Peki neden Foo ctor yerine float sürümü çağrılıyor?
Ekim’12’de

2
undu, hm hangi derleyiciyi kullanıyorsun? gcc 3.4, float'a dönüştürme olmadığından şikayet ediyor. FooBir sınıfa göre öncelikli olduğu için bir işlevi çağırmaya çalışır .

@aleguna aslında bu kodu çalıştırmayı denemedim, bu sadece (kötü) bir tahmindi: s Ama yine de sorumu yanıtladın, işlevin sınıftan öncelikli olduğunu bilmiyordum.
2012'de 16

1
@didierc no, Foo::Foo("hi")C ++ 'da izin verilmez.
Johannes Schaub -

44

Yapıcıyı özel yapın, ancak sınıfa bir oluşturma yöntemi verin.


9
-1: Bu, OP'nin problemini nasıl çözer? Hala yazabilir Foo::create();üzerindeFoo const & x = Foo::create();
Thomas Eding

@ThomasEding Sanırım haklısın, OP'nin temel problemini çözmüyor, sadece düşünmeye zorluyor ve yaptığı hatayı yapmamaya çalışıyor.
dchhetri

1
@ThomasEding, sistemi bozmak isteyen kızgın kullanıcılara karşı kendinizi koruyamazsınız. @ Ecatmur'un hack'iyle bile söyleyebilirsin std::common_type<Foo>::type()ve bir geçici elde edersin. Veya hatta typedef Foo bar; bar().
Johannes Schaub -

@ JohannesSchaub-litb: Ancak büyük fark, yanlışlıkla olup olmadığıdır. std::common_type<Foo>::type()Yanlışlıkla yazmanın neredeyse hiçbir yolu yoktur . Foo const & x = ...Kazayla terk etmek tamamen inandırıcı.
Thomas Eding

24

Bu bir derleyici hatasıyla sonuçlanmaz, ancak bir çalışma zamanı hatasıyla sonuçlanır. Yanlış bir zamanı ölçmek yerine, kabul edilebilir bir istisna elde edersiniz.

Korumak istediğiniz herhangi bir kurucu set(guard), çağrılan varsayılan bir argümana ihtiyaç duyar .

struct Guard {
  Guard()
    :guardflagp()
  { }

  ~Guard() {
    assert(guardflagp && "Forgot to call guard?");
    *guardflagp = 0;
  }

  void *set(Guard const *&guardflag) {
    if(guardflagp) {
      *guardflagp = 0;
    }

    guardflagp = &guardflag;
    *guardflagp = this;
  }

private:
  Guard const **guardflagp;
};

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

Özellikler şunlardır:

Foo f() {
  // OK (no temporary)
  Foo f1("hello");

  // may throw (may introduce a temporary on behalf of the compiler)
  Foo f2 = "hello";

  // may throw (introduces a temporary that may be optimized away
  Foo f3 = Foo("hello");

  // OK (no temporary)
  Foo f4{"hello"};

  // OK (no temporary)
  Foo f = { "hello" };

  // always throws
  Foo("hello");

  // OK (normal copy)
  return f;

  // may throw (may introduce a temporary on behalf of the compiler)
  return "hello";

  // OK (initialized temporary lives longer than its initializers)
  return { "hello" };
}

int main() {
  // OK (it's f that created the temporary in its body)
  f();

  // OK (normal copy)
  Foo g1(f());

  // OK (normal copy)
  Foo g2 = f();
}

Durumunda f2, f3ve dönüşü "hello"istediği olmayabilir. Fırlatmayı önlemek için guard, kopyanın kaynağı yerine şimdi koruma ayarını sıfırlayarak kopyanın kaynağının geçici olmasına izin verebilirsiniz . Şimdi, yukarıdaki işaretçileri neden kullandığımızı da anlıyorsunuz - esnek olmamızı sağlıyor.

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  Foo(Foo &&other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  Foo(const Foo& other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

İçin karakteristikleri f2, f3ve için return "hello"her zaman şimdi // OK.


2
Foo f = "hello"; // may throwBu, bu kodu asla kullanmamam için beni korkutmak için yeterli.
Thomas Eding

4
@thomas, yapıcıyı işaretlemenizi explicitve ardından bu tür bir kodu daha fazla derlememenizi öneririm . amaç geçici olanı teşvik etmekti ve öyle de yapıyor. Eğer korkuyorsanız, copy veya move yapıcısındaki bir kopyanın kaynağını geçici olmayacak şekilde ayarlayarak bunu atmamasını sağlayabilirsiniz. o zaman yalnızca birkaç kopyanın son nesnesi, hala geçici olarak sonuçlanıyorsa fırlatılabilir.
Johannes Schaub -

2
Tanrım. C ++ ve C ++ 11 konusunda acemi değilim, ancak bunun nasıl çalıştığını anlayamıyorum. Lütfen birkaç açıklama ekleyebilir misiniz? ..
Mikhail

6
@Mikhail, aynı noktalarda imha edilen geçici nesnelerin imha sırası, bunların inşa edilme sırasının tersidir. Arayanın geçtiği varsayılan argüman geçicidir. Eğer Foonesne çok geçici ve varsayılan argüman olarak aynı ifadede onun ömür boyu uçları, ardından Fooeski ikincisi sonra oluşturulduğu için nesnenin dtor, varsayılan argüman dtor önce çağrılır.
Johannes Schaub -

1
@ JohannesSchaub-litb Çok güzel bir numara. Gerçekten ayırt etmek imkansızdır düşünce Foo(...);ve Foo foo(...);içinden Foo.
Mikhail

18

Birkaç yıl önce GNU C ++ derleyicisi için bu durum için yeni bir uyarı seçeneği ekleyen bir yama yazdım . Bu bir Bugzilla öğesinde izlenir .

Ne yazık ki, GCC Bugzilla, iyi düşünülmüş yama dahil özellik önerilerinin yok olmaya gittiği bir mezarlık alanıdır. :)

Bu, bu sorunun konusu olan ve yerel nesneleri kilitleme ve kilit açma, yürütme süresini ölçme vb. Araçlar olarak kullanan kodda tam olarak bu tür hataları yakalama arzusundan kaynaklanıyordu.


9

Olduğu gibi, uygulamanızda bunu yapamazsınız, ancak bu kuralı kendi yararınıza kullanabilirsiniz:

Geçici nesneler, const olmayan referanslara bağlanamaz

Kodu sınıftan, const olmayan bir referans parametresi alan bağımsız bir işleve taşıyabilirsiniz. Bunu yaparsanız, geçici bir sabit olmayan referansa bağlanmaya çalışırsa bir derleyici hatası alırsınız.

Kod Örneği

class Foo
{
    public:
        Foo(const char* ){}
        friend void InitMethod(Foo& obj);
};

void InitMethod(Foo& obj){}

int main()
{
    Foo myVar("InitMe");
    InitMethod(myVar);    //Works

    InitMethod("InitMe"); //Does not work  
    return 0;
}

Çıktı

prog.cpp: In function int main()’:
prog.cpp:13: error: invalid initialization of non-const reference of type Foo&’ from a temporary of type const char*’
prog.cpp:7: error: in passing argument 1 of void InitMethod(Foo&)’

1
@didierc: Ek bir işlev sağladıkları sürece, bunu yapmamak size kalmış. Standart tarafından açıkça izin verilmeyen bir şeyi elde etmenin bir yolunu ayarlamaya çalışıyoruz, bu nedenle elbette kısıtlamalar olacaktır.
Alok

@didierc parametre xadlandırılmış bir nesnedir, bu yüzden onu gerçekten yasaklamak isteyip istemediğimiz net değildir. Kullanmış olacağınız kurucu açıksa, insanlar içgüdüsel olarak yapabilir Foo f = Foo("hello");. Bence başarısız olursa sinirleneceklerdi. Benim çözümüm başlangıçta onu (ve çok benzer davaları) bir istisna / iddia-başarısızlıkla reddetti ve biri şikayet etti.
Johannes Schaub -

@ JohannesSchaub-litb Evet, OP bir kurucu tarafından oluşturulan değerin bağlamaları zorlayarak atılmasını yasaklamak istiyor. Örneğim yanlış.
didierc

7

Basitçe varsayılan bir kurucuya sahip değilsiniz ve her yapıcıda bir örneğe başvuru gerektirir.

#include <iostream>
using namespace std;

enum SelfRef { selfRef };

struct S
{
    S( SelfRef, S const & ) {}
};

int main()
{
    S a( selfRef, a );
}

3
Güzel fikir, ama bir değişkene sahip yakında kadar: S(selfRef, a);. : /
Xeo

3
@Xeo S(SelfRef, S const& s) { assert(&s == this); }, eğer bir çalışma zamanı hatası kabul edilebilirse.

6

Hayır, korkarım bu mümkün değil. Ancak bir makro oluşturarak aynı etkiyi elde edebilirsiniz.

#define FOO(x) Foo _foo(x)

Bunu yerine getirdiğinizde, Foo my_foo (x) yerine FOO (x) yazabilirsiniz.


5
Olumlu oy verecektim ama sonra "bir makro yaratabilirsin" gördüm.
Griwes

1
Tamam, alt çizgiler düzeltildi. @Griwes - Köktendinci olmayın. "Bu yapılamaz" yerine "makro kullan" demek daha iyidir.
amaurea

5
Peki, bu yapılamaz. Problemi hiç çözmediniz, yine de yapılması tamamen yasal Foo();.
Puppy

11
Şimdi burada inatçısın. Foo sınıfını karmaşık bir şey olarak yeniden adlandırın ve makroyu Foo olarak adlandırın. Sorun çözüldü.
amaurea

8
Şunun gibi bir şey:class Do_not_use_this_class_directly_Only_use_it_via_the_FOO_macro;
Benjamin Lindley

4

Birincil amaç hataları önlemek olduğundan, şunu göz önünde bulundurun:

struct Foo
{
  Foo( const char* ) { /* ... */ }
};

enum { Foo };

int main()
{
  struct Foo foo( "hi" ); // OK
  struct Foo( "hi" ); // fail
  Foo foo( "hi" ); // fail
  Foo( "hi" ); // fail
}

Bu şekilde değişkeni adlandırmayı ve yazmayı unutamazsınız struct. Ayrıntılı, ama güvenli.


1

Bir parametrik kurucunun açık olduğunu ve hiç kimsenin istemeden o sınıfın bir nesnesini yaratmayacağını bildirin.

Örneğin

class Foo
{
public: 
  explicit Foo(const char*);
};

void fun(const Foo&);

sadece bu şekilde kullanılabilir

void g() {
  Foo a("text");
  fun(a);
}

ama asla bu şekilde (yığın üzerinde geçici olarak)

void g() {
  fun("text");
}

Ayrıca bakınız: Alexandrescu, C ++ Kodlama Standartları, Madde 40.


3
Bu izin veriyor fun(Foo("text"));.
Guilherme Bernal
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.