Вариации на тему деклараций
От: rg45 СССР  
Дата: 07.12.09 21:45
Оценка: 159 (16)
Привет всем!

Прошу прощения, если баян, но я, мягко говоря, удивлен.

Как вы считаете, имеет ли какой-нибудь смысл, например, такое объявление:
typedef int Foo(const char*) const;

и в какой области такое объявление может располагаться? Оказывается, имеет и может располагаться вне определения класса, не смотря на модификатор const.

А вот вполне себе well-formed программка, можете откомпилировать и запустить. Обратите внимание на объявления и определения свободных функций и функций-членов:
#include <string>
#include <iostream>

typedef std::string Foo(const char*);
typedef std::string Goo(const char*) const;

//Свободные функции
Foo f1;
Foo f2;
Foo f3;

std::string f1(const char* str) { return "f1 -- " + std::string(str); }
std::string f2(const char* str) { return "f2 -- " + std::string(str); }
std::string f3(const char* str) { return "f3 -- " + std::string(str); }

//Функции-члены
struct A
{
  Foo f1;
  Foo f2;
  Foo f3;
  
  Goo g1;
  Goo g2;
  Goo g3;
};

std::string A::f1(const char* str) { return "A::f1 -- " + std::string(str); }
std::string A::f2(const char* str) { return "A::f2 -- " + std::string(str); }
std::string A::f3(const char* str) { return "A::f3 -- " + std::string(str); }

std::string A::g1(const char* str) const { return "A::g1 -- " + std::string(str); }
std::string A::g2(const char* str) const { return "A::g2 -- " + std::string(str); }
std::string A::g3(const char* str) const { return "A::g3 -- " + std::string(str); }

//Использование
int main()
{
  A a;

  std::cout << f1("abc") << std::endl;
  std::cout << f2("def") << std::endl;
  std::cout << f3("ghi") << std::endl;

  std::cout << a.f1("jkl") << std::endl;
  std::cout << a.f2("mno") << std::endl;
  std::cout << a.f3("pqr") << std::endl;

  std::cout << a.g1("stu") << std::endl;
  std::cout << a.g2("vwx") << std::endl;
  std::cout << a.g3("yza") << std::endl;
}


А вот пункт стандарта, который все это благословляет: 9.3/9. Для тех, у кого нет под рукой стандарта цитирую:

[Note: a member function can be declared (but not defined) using a typedef for a function type. The resulting member function has exactly the same type as it would have if the function declarator were provided explicitly, see 8.3.5. For example,

typedef void fv(void);
typedef void fvc(void) const;
struct S {
fv memfunc1; // equivalent to: void memfunc1(void);
void memfunc2();
fvc memfunc3; // equivalent to: void memfunc3(void) const;
};
fv S::* pmfv1 = &S::memfunc1;
fv S::* pmfv2 = &S::memfunc2;
fvc S::* pmfv3 = &S::memfunc3;

Also see 14.3. ]

--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Вариации на тему деклараций
От: Тот кто сидит в пруду Россия  
Дата: 07.12.09 21:54
Оценка: 1 (1)
Здравствуйте, rg45, Вы писали:

R>Прошу прощения, если баян, но я, мягко говоря, удивлен.


R>Как вы считаете, имеет ли какой-нибудь смысл, например, такое объявление:

R>
R>typedef int Foo(const char*) const;
R>

R>и в какой области такое объявление может располагаться? Оказывается, имеет и может располагаться вне определения класса, не смотря на модификатор const.

Для объявления членов класса я такую фишку уже 5 лет использую, в самодельном rpc. Мне там тип члена нужен, который по другому выцарапать не удалось. Поэтому в одном макросе сначала объявляю тип члена, потом, пользуясь этим типом, саму функцию. Допускаю что кому нибудь в аналогичных целях может и вне класса пригодиться.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re: Вариации на тему деклараций
От: rg45 СССР  
Дата: 07.12.09 22:21
Оценка:
Здравствуйте, rg45, Вы писали:

R>Как вы считаете, имеет ли какой-нибудь смысл, например, такое объявление:

R>
R>typedef int Foo(const char*) const;
R>

R>...

Думаю, полезное применение для этой фичи найдется быстро. Например, для случаев, когда объявляется большое количество функций с одинаковой сигнатурой. Причем, прелесть в том, что один и тот же typedef одновременно используется как свободными функциями, так и функциями-членами классов, причем, разных классов! При необходимости одним махом можно изменить все объявления. Правда, определения все равно прийдется лопатить врукопашную, но тут уже копмилятор не даст ошибиться и пропустить какое-то определение.

Еще применение: это дополнительное средство повышения читабельности объявления указателей на функции члены. Например, вместо
struct A
{
  std::string foo(int, double, const char*) const;
};
struct B
{
  std::string foo(int, double, const char*) const;
};

typedef std::string (A::*A_Foo)(int, double, const char*) const;
typedef std::string (B::*B_Foo)(int, double, const char*) const;

A_Foo a_foo = &A::foo;
B_Foo b_foo = &B::foo;

можно писать:
typedef std::string Foo(int, double, const char*) const;

struct A { Foo foo; };
struct B { Foo foo; };

Foo A::*a_foo = &A::foo;
Foo B::*b_foo = &B::foo;
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Вариации на тему деклараций
От: Erop Россия  
Дата: 08.12.09 00:09
Оценка: 45 (6)
Здравствуйте, rg45, Вы писали:

R>Прошу прощения, если баян, но я, мягко говоря, удивлен.

Интересно, почему эту фичу редко используют для безопасного объявления виртуальных функций?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Вариации на тему деклараций
От: jazzer Россия Skype: enerjazzer
Дата: 08.12.09 10:37
Оценка: 13 (2)
Здравствуйте, rg45, Вы писали:

R>Привет всем!


R>Прошу прощения, если баян, но я, мягко говоря, удивлен.


R>Как вы считаете, имеет ли какой-нибудь смысл, например, такое объявление:

R>
R>typedef int Foo(const char*) const;
R>

R>и в какой области такое объявление может располагаться? Оказывается, имеет и может располагаться вне определения класса, не смотря на модификатор const.

gcc 3.4.6 не поддерживает консты
e.cpp:189: error: invalid type qualifier for non-member function type
e.cpp:189: error: `const' and `volatile' function specifiers on `Foo' invalid in type declaration



Как обычно, макросы нас спасут (и заодно будут работать и для определений, не только для объявлений) :
#define DECLARE(sig, name) sig(,name)
#define DEFINE(sig, class, name) sig(class::,name)

// signatures
#define SIG1(class, name) void class name(int i, double d)
#define SIG2(class, name) void class name(double d, int i) const

// better name for SIG1
#define DECLARE_CALLBACK(name) DECLARE(SIG1, name)
#define DEFINE_CALLBACK(class, name) DEFINE(SIG1, class, name)

struct A
{
  DECLARE(SIG1, f1);
  DECLARE(SIG2, g);
  
  DECLARE_CALLBACK(f2);
};

DEFINE(SIG1, A, f1)
{
}

DEFINE(SIG2, A, g)
{
}

DEFINE_CALLBACK(A, f2)
{
}


брюки превращаются вот в такой код:
struct A
{
  void f1(int i, double d);
  void g(double d, int i) const;

  void f2(int i, double d);
};

void A:: f1(int i, double d)
{
}

void A:: g(double d, int i) const
{
}

void A:: f2(int i, double d)
{
}
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Вариации на тему деклараций
От: rg45 СССР  
Дата: 08.12.09 11:07
Оценка: +1
Здравствуйте, rg45, Вы писали:

R>Прошу прощения, если баян, но я, мягко говоря, удивлен.

R>...

Еще маленький нюанс. Также, как для объявленных "традиционным" образом перегруженных функций-членов, при таком способе объявления также допустимо повторное использование идентификаторов:
typedef void X();
typedef void Y() const;
typedef void Z(int);

struct A
{
  X foo;
  Y foo;
  Z foo;
};
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Вариации на тему деклараций
От: rg45 СССР  
Дата: 08.12.09 11:12
Оценка: +1
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, rg45, Вы писали:


R>>Прошу прощения, если баян, но я, мягко говоря, удивлен.

E>Интересно, почему эту фичу редко используют для безопасного объявления виртуальных функций?

Я вообще никогда раньше не встречал таких объявлений функций-членов, ни виртуальных ни невиртуальных. А выглядит, конечно, непривычно:
struct A
{
  virtual Foo foo = 0;
};
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Вариации на тему деклараций
От: yatagarasu Беларусь  
Дата: 08.12.09 11:15
Оценка:
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, rg45, Вы писали:


R>>Прошу прощения, если баян, но я, мягко говоря, удивлен.

E>Интересно, почему эту фичу редко используют для безопасного объявления виртуальных функций?

а в чем безопасность? и опасность старого метода?
Re[3]: Вариации на тему деклараций
От: Erop Россия  
Дата: 08.12.09 11:23
Оценка:
Здравствуйте, yatagarasu, Вы писали:

Y>а в чем безопасность? и опасность старого метода?


В том, что поменяешь тип функции в базе, а в каком-нибудь из наследников забудешь...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Вариации на тему деклараций
От: rg45 СССР  
Дата: 08.12.09 11:24
Оценка: +1 :)
Здравствуйте, yatagarasu, Вы писали:

Y>Здравствуйте, Erop, Вы писали:


E>>Здравствуйте, rg45, Вы писали:


R>>>Прошу прощения, если баян, но я, мягко говоря, удивлен.

E>>Интересно, почему эту фичу редко используют для безопасного объявления виртуальных функций?

Y>а в чем безопасность? и опасность старого метода?


В том, что легко допустить незначительную ошибку в сигнатуре и вместо переопределения виртуальной функции получить сокрытие, например:
class A
{
public:
  virtual int foo(int) const;
};

class B : public A
{
public:
  virtual int foo(int); //Забыли модификатор const и получили новую функцию вместо перекрытия существующей
};

И хорошо, если компилятор выдаст предупреждение.

А такой вариант дает больше уверенности:
typedef int Foo(int) const;
class A
{
public:
  virtual Foo foo;
};

class B : public A
{
public:
  virtual Foo foo; //virtual можно даже не писать в этом месте, его обычно пишут для большей наглядности
};
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[3]: Вариации на тему деклараций
От: jazzer Россия Skype: enerjazzer
Дата: 08.12.09 11:24
Оценка: +1
Здравствуйте, yatagarasu, Вы писали:

Y>а в чем безопасность? и опасность старого метода?


В том, что если ты ошибаешься в сигнатуре перекрытого метода, то он перестаёт быть перекрытым и становится перегруженным, скрывая оригинал.
Со всеми вытекающими.
Ну и аналогично при смене сигнатуры в базовом классе.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: Вариации на тему деклараций
От: Pavel Dvorkin Россия  
Дата: 08.12.09 11:41
Оценка: 7 (1)
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, Erop, Вы писали:


E>>Здравствуйте, rg45, Вы писали:


R>>>Прошу прощения, если баян, но я, мягко говоря, удивлен.

E>>Интересно, почему эту фичу редко используют для безопасного объявления виртуальных функций?

R>Я вообще никогда раньше не встречал таких объявлений функций-членов, ни виртуальных ни невиртуальных. А выглядит, конечно, непривычно:

R>
R>struct A
R>{
R>  virtual Foo foo = 0;
R>};
R>


Гм...

Берем твой пример и делаем так


struct A
{
  virtual Foo h = 0;
  Foo f1;
  Foo f2;


1>4777777.obj : error LNK2001: unresolved external symbol "public: virtual class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __thiscall A::h(char const *)" (?h@A@@UAE?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@PBD@Z)


А теперь так, явно


struct A
{
  virtual std::string Moo(const char*) = 0;
  Foo f1;
  Foo f2;


1>e:\4777777\4777777.cpp(45) : error C2259: 'A' : cannot instantiate abstract class


VC 2008

Получается, что в первом случае класс не явялется абстрактным, несмотря на наличие чисто вирт. функции ?
With best regards
Pavel Dvorkin
Re[4]: доигрались
От: Pavel Dvorkin Россия  
Дата: 08.12.09 11:47
Оценка: 14 (2) +2

struct A
{
  virtual Foo h = 0;
  Foo f1;
  Foo f2;
  Foo f3;
  
  Goo g1;
  Goo g2;
  Goo g3;
};

std::string A::h(const char* p)
{
    return p; // sorry! :-)
}


========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

Это как все понимать ?
With best regards
Pavel Dvorkin
Re[5]: доигрались
От: rg45 СССР  
Дата: 08.12.09 12:11
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


PD>

PD>struct A
PD>{
PD>  virtual Foo h = 0;
PD>  Foo f1;
PD>  Foo f2;
PD>  Foo f3;
  
PD>  Goo g1;
PD>  Goo g2;
PD>  Goo g3;
PD>};

PD>std::string A::h(const char* p)
PD>{
PD>    return p; // sorry! :-)
PD>}

PD>


PD>========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========


PD>Это как все понимать ?


Тебя удивляет, что компилятор не возражает против определения чисто виртуальной функции? Ты еще больше удивишься когда узнаешь, что это полностью соответствет стандарту, см. 10.4/2. Этот вопрос не раз обсуждался на форума и в Q&A есть ссылка: А чисто виртуальные деструкторы &mdash; бывают?
Автор: Павел Кузнецов
Дата: 03.04.03
.А еще можешь переделать свой пример для "традиционнго" объявления, ты убедишься, что в этом случае можно определить чисто виртуальную функцию. Только, на всякий случай, определение чисто виртуальной функции болжно быть выполнено за пределами определения класса согласно все тому же пункту стандарта.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[5]: доигрались
От: yatagarasu Беларусь  
Дата: 08.12.09 12:18
Оценка: 14 (1)
Здравствуйте, Pavel Dvorkin, Вы писали:


PD>

PD>struct A
PD>{
PD>  virtual Foo h = 0;
PD>  Foo f1;
PD>  Foo f2;
PD>  Foo f3;
  
PD>  Goo g1;
PD>  Goo g2;
PD>  Goo g3;
PD>};

PD>std::string A::h(const char* p)
PD>{
PD>    return p; // sorry! :-)
PD>}

PD>


PD>========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========


PD>Это как все понимать ?


C:/devkitPro/projects/sandbox/test/src/serialization.h:200: error: cannot allocate an object of abstract type 'A'
C:/devkitPro/projects/sandbox/test/src/serialization.h:41: note: because the following virtual functions are pure within 'A':
C:/devkitPro/projects/sandbox/test/src/serialization.h:111: note: virtual bool A::h(const char *)
gcc 4.4

msvc 2010 beta 2 ведет себя так же как и 2008
Re[5]: таки да, доигрались
От: Alexander G Украина  
Дата: 08.12.09 13:07
Оценка: 14 (1)
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Это как все понимать ?


Нужно полностью код приводить
тут баг не то что бывают чисто виртуальные функции, а то, что с ними можно создать экземпляр класса.

pure specifier, видимо, почти игнорируется.

typedef void G (int);

struct X
{
    G f1 = 0;
};

int main()
{
    X y;
}


и даже


typedef void G (int);

union X
{
    G f1 = 666;
};

int main()
{
    X y;
}
Русский военный корабль идёт ко дну!
Re[6]: таки да, доигрались
От: wander  
Дата: 08.12.09 13:20
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>Это как все понимать ?


AG>Нужно полностью код приводить

AG>тут баг не то что бывают чисто виртуальные функции, а то, что с ними можно создать экземпляр класса.

AG>pure specifier, видимо, почти игнорируется.


AG>
AG>typedef void G (int);

AG>struct X
AG>{
AG>    G f1 = 0;
AG>};

AG>int main()
AG>{
AG>    X y;
AG>}
AG>


AG>и даже



AG>
AG>typedef void G (int);

AG>union X
AG>{
AG>    G f1 = 666;
AG>};

AG>int main()
AG>{
AG>    X y;
AG>}
AG>


В каком компиляторе? У меня в mingw 4.4 все прекрасно. Выдает ошибку как и положено.
Re[7]: таки да, доигрались
От: Alexander G Украина  
Дата: 08.12.09 13:47
Оценка:
Здравствуйте, wander, Вы писали:


W>В каком компиляторе? У меня в mingw 4.4 все прекрасно. Выдает ошибку как и положено.


default compiler

конкретно, в моём случае MSVC8 (VC 2005).
Русский военный корабль идёт ко дну!
Re[8]: таки да, доигрались
От: wander  
Дата: 08.12.09 13:59
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>Здравствуйте, wander, Вы писали:



W>>В каком компиляторе? У меня в mingw 4.4 все прекрасно. Выдает ошибку как и положено.


AG>default compiler


AG>конкретно, в моём случае MSVC8 (VC 2005).


Подозреваю, что эта проблема наблюдается только в MSVC.
Re[8]: таки да, доигрались
От: Тот кто сидит в пруду Россия  
Дата: 08.12.09 14:06
Оценка:
Здравствуйте, Alexander G, Вы писали:

W>>В каком компиляторе? У меня в mingw 4.4 все прекрасно. Выдает ошибку как и положено.


AG>default compiler


AG>конкретно, в моём случае MSVC8 (VC 2005).


7.1 кажись ругался.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.