LINUX.ORG.RU

[творчество, C++] Variable is unbound


0

2

Написал тут небольшой шаблонный класс, позволяющий отслеживать использование неинициализованных переменных. Основной use case — проверка того, что все явно созданные переменные имеют контролируемые значения, т.е. исключается, что они получили какие-то значения по-умолчанию. Может быть полезен при отладке. Вот текст. Ругайте

#include <iostream> 
#include <stdexcept> 

using namespace std;

template <typename T>
class Variable
{
  private:
           bool is_initialized;
           T    value;
  public:
          Variable()
           :is_initialized(false) {};

          Variable(const Variable& rhs)
          {
            if(rhs.is_initialized)
            {
              is_initialized = true;
              value = rhs.value; // call copy constructor of T class
            }
            else
              is_initialized = false;
          };

          Variable& operator=(const Variable& rhs)
          {
            if(rhs.is_initialized)
            {
              is_initialized = true;
              value = rhs.value; // call T.operator=() or T(const T& rhs)
            }
            else
              is_initialized = false;

            return *this;
          };

          Variable(const T& rhs) // create Variable<T> object from T
           :is_initialized(true), value(rhs) {};

          operator T()           // Type conversion to use Variable<T> instead of T everywhere
          {
            if(is_initialized)
              return value;
            else
              throw invalid_argument("Variable is unbound");
          };
};

int
main(void)
{
  double p = 0.1;
  Variable<double> q; // Create unbound variable
  Variable<double> r = 0.0; // Create bound variable with appropriate value
  
// can assign
  r = p;
  cout << r << endl;
  double s = 2*r;
  cout << s << endl;
  
// using unbound variable causes error
  cout << p*q << endl;

  return 0;
}

Наверное можно где-нибудь в дебажном хедере сделать define'ы типа

#define double Variable<double>
anonymous ()
Ответ на: комментарий от anonymous

Можно. Но тут вот хитрость есть: скажем, формальные аргументы функций проверять не надо — они всегда связаны. Так что скорее лучше так: сначала везде писать Variable<T>, а перед сдачей проекта заказчику сделать sed s/Variable<\(.*\)>/\1/ :)

annoynimous ★★★★★ ()
Ответ на: комментарий от anonymous

Не соответствует семантике.

Всмысле, лучше отдать предпочтение явному get() с более подходящей по семантике реализацией, чем неявный.

anonymous ()

Я сто лет не упражнялся в плюсах... А точно operator T, а не operator T& ?

svu ★★★★★ ()

В целом всё хорошо. Могу поругать стиль кодирования.

1. using namespace лучше избегать в любом коде.

2. Точка с запятой после области видимости не ставится. Понимать как она лишняя после закрывающей фигурной скобки функции.

3. В копирующем конструкторе наблюдаем следующее:

if(rhs.is_initialized)
    is_initialized = true;
else
    is_initialized = false;

Как сократить такой код думаю догадаетесь сами.

4. Что такое rhs? Не забывайте, что код пишется для человека. Я, как человек, расшифровать эту аббревиатуру не в состоянии.

5. Избегайте else после return для читабельности.

if(!is_initialized)
    throw invalid_argument("Variable is unbound");
return value;

6. В функции без аргументов void лишний.

Dendy ★★★★★ ()

> Написал тут небольшой шаблонный класс, позволяющий отслеживать использование неинициализованных переменных

т.е. делает то, что в MSVC включено по умолчанию? а также то что отслеживается любым нормальным статическим анализатором? может лучше занятся делом и написать пачку патчей к gcc, чтоб реализовать то, что есть в других компиляторах?

anonymous ()
Ответ на: комментарий от anonymous

> т.е. делает то, что в MSVC включено по умолчанию? а также то что отслеживается любым нормальным статическим анализатором?

Для многих классов (типов) определены конутрукторы по-умолчанию, поэтому объявление Type s; совершенно валидны; мой код позволяет отслеживать каждое такое использование. Не знаю, что уж там делает MSVC, но если он выдает варнинги на объявление double s; — его надо выкинуть и больше никогда им не пользоваться (за одним, кажется, исключением, если s — глобальная переменная, вроде они по стандарту не должны инициализоваться).

может лучше занятся делом и написать пачку патчей к gcc, чтоб реализовать то, что есть в других компиляторах?

А что есть в других?

annoynimous ★★★★★ ()

Неплохо было бы заюзать везде call_traits из boost

yoghurt ★★★★★ ()
Ответ на: комментарий от Dendy

> 1. using namespace лучше избегать в любом коде.

Это просто потому, что пример сразу и рабочий образец. Кстати, а как постыпать в продакшн? Неужеди везде держать эти std::? Вам посимвольно платят? :)

2. Точка с запятой после области видимости не ставится. Понимать как она лишняя после закрывающей фигурной скобки функции.

Что не так? Этот код компилируется -Wpedatic, вроде он ни одной лишней ";" не пропускает

annoynimous ★★★★★ ()
Ответ на: комментарий от anonymous

> Learn to read. Он писал про использование не инициализированных переменных.

Вроде по-стандарту числовые типы всегда инициализируются нулем. Как отследить, что этот неявный нуль не то, что надо?

annoynimous ★★★★★ ()
Ответ на: комментарий от annoynimous

> Вроде по-стандарту числовые типы всегда инициализируются нулем

brutal_facepalm.ogv

anonymous ()
Ответ на: комментарий от annoynimous

> Вам посимвольно платят? :)

using namespace порождает потенциальные конфликты как между модулями для компилятора, так и в голове читателя кода. Это тот string, который из std:: или из другого модуля или тот, кому я сделал typedef? В больших проектах, над которыми трудятся много людей, понятность с первого взгляда превыше всего.

Этот код компилируется -Wpedatic, вроде он ни одной лишней ";" не пропускает


Как видим - пропускает. Попробуйте вместо ";" поставить ";;;;".

Dendy ★★★★★ ()
Ответ на: комментарий от annoynimous

Числовые типы инициализируются тем что в данный момент находится в выделенный под объект памяти.

KblCb ★★★★★ ()
Ответ на: комментарий от annoynimous

Сходу в стандарте не нашёл. Во всяком случае вот такой код:

#include <iostream>

int main( int argc, char** argv )
{
    int i;

    std::cout << i << std::endl;

    return 0;
}
Выдаёт случайные ненулевые значения при использовании g++, одно (для каждого компилятора) ненулевое значение при использование clang++ и SolarisStudio и 0 для icc. Это даже интересно.

KblCb ★★★★★ ()
Ответ на: комментарий от KblCb

По стандарту(С++) простые типы не инициализируются за исключением статичных и глобальных переменных, если я правильно помню.

anonymous ()
Ответ на: комментарий от KblCb
$ g++ -O2 -Wall -pedantic -Wextra -o test test.cc 
test.cc:3: warning: unused parameter ‘argc’
test.cc:3: warning: unused parameter ‘argv’
test.cc: In function ‘int main(int, char**)’:
test.cc:7: warning: ‘i’ is used uninitialized in this function
$ ./test 
0

Самое интересное, если убрать -O2, то сообщение про неинициализованную переменную пропадает, равно как и ответ становится случайным.

annoynimous ★★★★★ ()
Ответ на: комментарий от linuxfan

Безграмотный анонимус такой безграмотный.

ты дебил и не лечишься

void MyFunc( int& var );
...

int someVar;
MyFunc( someVar );
printf( "%d\n", someVar );

такие вещи можно отследить только в рантайме

anonymous ()
Ответ на: комментарий от annoynimous

ISO/IEC 14882:2003

8 Declarators
8.5 Initializers

(9)
If no initializer is specified for an object, and the object is of (possibly cv-qualified) non-POD class type (or array thereof), the object shall be default-initialized; if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor. Otherwise, if no initializer is specified for a non-static object, the object and its subobjects, if any, have an indeterminate initial value⁹⁰⁾; if the object or anyof its subobjects are of const-qualified type, the program is ill-formed.

__________________________
⁹⁰⁾ This does not apply to aggregate objects with automatic storage duration initialized with an incomplete brace-enclosed initializer-list; see 8.5.1.

arsi ★★★★★ ()
Ответ на: комментарий от anonymous

>такие вещи можно отследить только в рантайме

Такие вещи как раз прекрасно выявляются статическим анализатором кода (при условии, что доступен код MyFunc. Если в MyFunc есть ветвление и в var пишется только в одной ветке, то при отладке всегда будет вероятность недоглядеть и пропустить ветку с ошибкой.

Уж что-что, а инициализацию можно и нужно проверять по исходному коду,а не в рантайме.

linuxfan ()
Ответ на: комментарий от linuxfan

Да в общем-то никто и не спорит, что можно на этапе компиляции, хотя повторю, что для классов с безаргументным конструктором ни один компилятор не пикнет, потому что это синтаксически абсолютно корректно.

annoynimous ★★★★★ ()
Ответ на: комментарий от annoynimous

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

и он будет совершенно прав. хотите класс ХХХ без дефолтного конструктора? создайте что-то типа class SafeXXX : public XXX { explicit SafeXXX() {} } (или как там это правильно в плюсах пишется) и спите спокойно…

arsi ★★★★★ ()
Ответ на: комментарий от arsi

Блин. Никто и не говорит, что создана серебряная пуля. Просто сделана обертка, позволяющая следить за инициализацией вне зависимости от того, есть ли доступ к исходному коду используемых классов и требующий минимального изменения кода в процессе написания, которое (изменение) можно потом легко откатить после окончания цикла тестирования :) Если угодно, просто идея.

annoynimous ★★★★★ ()
Ответ на: комментарий от annoynimous

вопрос в другом: есть классы, «легально» инициализирующие свои инстанции при создании корректным дефолтом; есть примитивы, которые этого не делают. вот убей не могу понять, зачем следить за инициализацией классов с implicit-конструкторами (ведь их именно такими и задумывали)? или зачем следить за примитивами, если за ними [вполне успешно] следит компилятор?

arsi ★★★★★ ()

сразу 2 замечания:

1. ты воткнул is_initialized в инстанс, а это значит поползет sizeof, и какие-то шаблоны, зависящие от sizeof могут инстанциироваться по-другому, не говоря уж о другой укладке в памяти

это решаемо, но я вижу решение только для типов не короче 4 байт, или хотя бы 2 байт (если у нас данных мало)

2. если это предполагается вставлять в код sed-ом, то можно дополнить

template<class T> struct My { typedef T Variable; };

template<> struct My { typedef Anoynimus::Variable<double> Variable; };

чтобы отслеживать допустим только double, или наоборот (аналогично) чтобы не отслеживать какие-то типы

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от arsi

> если за ними [вполне успешно] следит компилятор?

Даже за вот такими http://www.linux.org.ru/jump-message.jsp?msgid=5485699&cid=5486730 где цепочка функций составляет 2-3? Респект разработчикам компилятора! Стек они тоже бесконечный создавать умеют? :)

annoynimous ★★★★★ ()
Ответ на: комментарий от annoynimous

> Даже за вот такими http://www.linux.org.ru/jump-message.jsp?msgid=5485699&cid=5486730

это «фича» языка, не путай. и это не противоречит тому, что я сказал ранее. или ты о чём вообще?

> Стек они тоже бесконечный создавать умеют? :)

здрассе, а он здесь при чём?

arsi ★★★★★ ()
Ответ на: комментарий от annoynimous

поползший sizeof это критично

вот например такой код будет пропущен как рабочий твоим чекером при T=Variable<int>, хотя он глючит при T=int

template<class T> T f(T x)
{
  if( sizeof(T)<=4 )
    T not_initalized_var;
    return  not_initalized_var+x;
  }else
    T initalized_var=0;
    return  initalized_var+x;
  }
}

реально понятно будет не так просто — но разный способ организации хэша в зависимости от sizeof хранимого значения — это реальность

www_linux_org_ru ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.