Страницы

Поиск по вопросам

Показаны сообщения с ярлыком инициализация. Показать все сообщения
Показаны сообщения с ярлыком инициализация. Показать все сообщения

воскресенье, 15 марта 2020 г.

Присвоение значения переменной при объявлении в классе

#cpp #классы #инициализация


Есть класс:

class A
{
  public: 
      static int count;

      A()
      {
          count++; // При создании каждого объекта эта переменная должна увеличиваться
      }
};


Надо присвоить значение этой переменной 0, до вызова конструктора. Но если это сделать
вот так:

static int count = 0;


То компилятор ругается. Как обойтись в такой ситуации?
    


Ответы

Ответ 1



Просто надо правильно определить эту переменную вне класса class A { public: static int count; A() { count++; // При создании каждого объекта эта переменная должна увеличиваться } }; //... int A::count = 0; Вы также можете опустить инициализатор, так как переменная в любом случае будет инициализирована нулем: int A::count; Если определение класса помещено в заголовочный файл, то определение статической переменной нужно поместить в один из программных модулей.

воскресенье, 8 марта 2020 г.

Компилятор не поддерживает расширенную инициализацию

#cpp #gcc #cpp11 #инициализация


Имеется такой код:

int main() {
    double d {4.5};
    int x {d};
}


Прочитал, что в новой версии С++11 есть поддержка на запрет сужающих преобразований
при инициализации. Но компилятор ошибки не выдает, хотя должен (ибо я пытаюсь double
в int преобразовать с синтаксисом новой версии инициализации (с помощью фигурных скобок),
которая как раз запрещает подобные небезопасные преобразования. 

IDE - DevC++ 5.11
Компилятор - TDM-GCC 4.9.2.

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


Ответы

Ответ 1



Стандарт С++ не обязывает реализацию языка выдавать именно "ошибку компиляции" для случаев когда в стандарте написано что код ошибочен. (Однако при этом не должно ломается SFINAE, иначе это уже баг компилятора). Компилятор g++ в некоторых случаях ограничивается предупреждением: double d = 4.5; int x{d}; // предупреждение в g++ int y{4.5}; // ошибка в g++ Впрочем этого достаточно, т.к. рекомендуется компилировать код с -Wall -pedantic -Werror. Что касается стандарта, то int x{d}; это несомненно неправильный код. В разделе [dcl.init.list] есть аналогичный пример.

воскресенье, 9 февраля 2020 г.

Как инициализировать объект через список инициализации

#cpp #cpp11 #инициализация


Есть такой класс:

class Book {
public:
    char* name;
    char** authors;
    int authorsCount;
    int publishingYear;
    Book(const char* name_,\
         const char* authors[],\
         int authorsCount_, \
         int publishingYear_);
    bool operator < (const Book &other) const;
    void print(void) const;
    ~Book();
};


Как правильно(и возможно ли вообще) инициализировать объект этого класса используя
список инициализации(Braced initialization)? Вот такой вариант почему-то не работает:

Book b = {"Skazki", {"Ivanov", "Petrov"}, 2, 1968};



  error: could not convert ‘{"Skazki", {"Ivanov", "Petrov"}, 2, 1968}’
  from ‘’ to ‘Book’
       Book b = {"Skazki", {"Ivanov", "Petrov"}, 2, 1968};

    


Ответы

Ответ 1



Вы пытаетесь инициализировать указатель const char **authors при помощи инициализатора {"Ivanov", "Petrov"}. Компилятору, разумеется, совершенно не ясно, что вы пытаетесь сделать. Конструировать в такой ситуации временный массив язык не будет. Чтобы в такой ситуации заставить компилятор сконструировать временный объект типа массив из {}-инициализатора, придется явно указать перед ним имя "массивного" типа. При этом "сложные" имена типов в таком контексте неупотребимы, т.е. придется сначала завернуть тип массив в компактное typedef-имя, например так using A = const char *[]; Book b = {"Skazki", A{"Ivanov", "Petrov"}, 2, 1968}; Если бы у вас в конструкторе был параметр типа const char* const (&authors)[2] то компилятор бы самостоятельно расценил ваше {"Ivanov", "Petrov"} как инициализатор для временного массива типа const char *[2]. Но вам этот вариант не подходит, ибо в вашем случае размер массива является значением времени выполнения. Что интересно, GCC не хочет компилировать и вариант с A{"Ivanov", "Petrov"}, ибо не разрешает брать адрес временного массива. Причины такого запрета не ясны. Вроде в стандарте его навскидку не видно. И Clang успешно компилирует этот вариант.

Ответ 2



Во-первых следует изменить поля класса чтобы они могли хранить передаваемые таким образом данные. Указатели char * должны быть const char * (или string_view) так как они указывают на неизменяемые массивы со статическим временем жизни полученные из строковых литералов. Указать char * * должен стать контейнером, так как передаваемый массив в любом случае будет временным, а нужно, чтобы он жил на протяжении жизни создаваемого объекта. private: using Authors = ::std::vector; private: char const * m_name; private: Authors m_authors; private: int m_publishingYear; public: explicit Book ( char const * const name , ::std::initializer_list const authors , int const publishingYear ) : m_name{name} , m_authors{authors} , m_publishingYear{publishingYear} {} Book b{"Skazki", {"Ivanov", "Petrov"}, 1968}; Online compiler

Почему статический блок инициализации выполнился после конструктора?

#java #инициализация #classloader


Доброго времени суток коллеги!

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

public class Singleton {
 static Singleton st = new Singleton();
static { //статический блок
    System.out.println("Статический блок");
}

{ //анонимный блок
    System.out.println("Динамический блок");
}

Singleton(){ //конструктор
    System.out.println("Конструктор");

}
public static void main(String[] args) {
   //Пустая точка входа
}
}


OUTPUT:

Динамический блок
Конструктор
Статический блок


Вопрос: Как объяснить наблюдаемое поведение?
    


Ответы

Ответ 1



Давайте по порядку посмотрим: Первым у вас объявлено статическое поле: static Singleton st = new Singleton(); Его нужно проинициализировать, соответственно вызывается конструктор: Singleton() { System.out.println("Конструктор"); } Но перед вызовом конструктора есть динамический блок инициализации, поэтому сперва инициализация объекта происходит в этом блоке, а потом уже выполняется конструктор: { //анонимный блок System.out.println("Динамический блок"); } //затем вызывается непосредственно конструктор Итак, первая строчка класса готова, статическое поле static Singleton st проинициализировано, далее уже инициализируем статический блок. Можете, на основе моего ответа предположить, что будет если в статический блок прописать ещё один вызов конструктора Singleton? Предположите, и можете попробовать затем и сравнить своё предположение. UPD. Нельзя сказать что static блок вызывается перед динамическим блоком. Они вызываются при разных условиях. Static блок вызывается после загрузки класса class loader'oм, а динамический блок вызывается при создании экземпляра класса. То есть static - инициализация класса, dynamic - инициализация экземпляра класса. И динамический блок в вашем конкретном примере вызывается во время инициализиции экземляра Singleton в первой строчке, которая static поле.

Ответ 2



Инициализация статических полей и статические блоки выполняются в порядке их объявления в классе. Поскольку у вас статическое поле static Singleton st = new Singleton(); объявляется первым, то оно инициализируется в первую очередь. При инициализации поля создается объект класса, но выполнение статик блока при создании объекта пропускается, потому что его выполнение начнется только после инициализации объявленного первым статического поля. Соответственно, при инициализации данного поля игнорируется статик блок, выполняется "Динамический блок", затем конструктор класса, поле теперь проинициализировано, и, в соответствии с порядком объявления, выполняется статический блок. http://www.quizful.net/post/java-fields-initialization

суббота, 8 февраля 2020 г.

Java. Задание занчения (final) переменной в try-блоке и ее дальнейшее использование и видимость

#java #переменные #инициализация #try_catch


Проблема заключается в необходимости задания значения final переменной (connectionSocket2)
в try-блоке. В дальнейшей части кода (в части run()) этого не видно и возникает как
бы ошибка, что та переменная не определена:

        final Socket connectionSocket;
        try { connectionSocket = welcomeSocket.accept(); }



  The local variable connectionSocket2 may not have been initialized


убрать final не могу, так как (в части run()) дает ошибку 


  Cannot refer to the non-final local variable connectionSocket defined
  in an enclosing scope


            Socket connectionSocket2=null;
            try { connectionSocket2 = welcomeSocket.accept();
            } catch (IOException e2) { e2.printStackTrace(); }
            final Socket connectionSocket=connectionSocket2; try{connectionSocket2.close();}catch(IOException
e1){e1.printStackTrace();}

            service.submit(new Runnable() {
                public void run() {
                    while (true) {
                        BufferedReader inFromClient=null;
                        DataOutputStream outToClient=null;
                        try{
                           inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
                           outToClient = new DataOutputStream(connectionSocket.getOutputStream());
                           outToClient.writeBytes(inFromClient.readLine());
                        } catch(IOException ioe) {} }}});


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

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

Как правильно, профессионально быть в этой ситуации? Модно ли как-то сказать компилятору,
что переменная на самом деле уже определена в try блоке и ему нечего "волноваться"?
(типа динамическая переменная, как в .Net)
    


Ответы

Ответ 1



Ответ-вопросник и очередной наброс на медальку. Итак, у вас было что-то вот такое: public class ICanIntoSockets { public static void main( String[] args ) throws IOException { ServerSocket welcomeSocket = new ServerSocket(10000); ExecutorService service = Executors.newCachedThreadPool(); doWork( welcomeSocket, service ); } public static void doWork( ServerSocket welcomeSocket, ExecutorService service ) { try { while (true) { final Socket connectionSocket = welcomeSocket.accept(); service.execute( () -> { try ( Socket socket = connectionSocket; BufferedReader inFromClient = new BufferedReader( new InputStreamReader(socket.getInputStream())); DataOutputStream outToClient = new DataOutputStream( socket.getOutputStream())) { outToClient.writeBytes(inFromClient.readLine()); } catch (IOException ioe) { ioe.printStackTrace(); } }); } } catch ( IOException ex ) { ex.printStackTrace(); } } } но try - плохо, и торморзит на тысячах подключений (тесты где?), поэтому вы решили от него избавиться. Ява - убогий язык, в ней зачем-то придумали Checked Exceptions и напихали во все места в стандартной библиотеке, поэтому совсем без try - никак: public static void doWork( ServerSocket welcomeSocket, ExecutorService service ) { while (true) { final Socket connectionSocket; try { connectionSocket = welcomeSocket.accept(); } catch (IOException ex) { ex.printStackTrace(); } service.execute(() -> { try ( Socket socket = connectionSocket; // The local variable connectionSocket may not have been initialized BufferedReader inFromClient = new BufferedReader( new InputStreamReader(socket.getInputStream())); DataOutputStream outToClient = new DataOutputStream(socket.getOutputStream())) { outToClient.writeBytes(inFromClient.readLine()); } catch (IOException ioe) { ioe.printStackTrace(); } }); } } Зло загнано в угол в одной строчке кода! Но компилятор почему-то считает, что переменная connectionSocket может быть не инициализирована. Почему? Потому что есть путь выполнения программы, при которой она действительно не инициализируется: когда welcomeSocket.accept() выбрасывает исключение. Метод не возвращает значение - значение переменной не присваивается. Что же делать? Не надо продолжать выполнение итерации, ваш код все равно не сможет работать дальше без клиентского сокета. Сделайте внутри catch-блока return, break, continue (в надежде, что следующий accept не выбросит исключение, что вряд ли). Если код непременно должен продолжаться дальше - присвойте connectionSocket null и где-то сделайте проверку. Предложенный вариант с массивом - это либо то самое создание объектов и выделение памяти, с которым вы сражаетесь, либо потеря подключений и обработка одного подключения несколько раз, смотря где вы этот массив объявите.

Ответ 2



Можно обёртку сделать: public class MySocket{ private Socket mSocket; public MySocket(){ } public void setSocket(Socket socket){ mSocket = socket; } public Socket getSocket(){ return mSocket; } } И создать его экземпляр: final MySocket connectionSocket = new MySocket(); И дальше: try{ connectionSocket.setSocket(welcomeSocket.accept()); } catch (IOException e2) { e2.printStackTrace(); } И внутри Runnable обращаться к connectionSocket.getSocket().

Ответ 3



создайте final Socket[] на один элемент и в пишите в него.

понедельник, 3 февраля 2020 г.

Инициализация View в классе Activity

#android #оптимизация #инициализация


Пытаюсь инициализировать View через метод findViewById(), прямо в классе MainActivity,
чтобы затем, при многократном использовании одного и того же View через onClick, метод
findViewById() не вызывался много раз, но при инициализации прямо в классе MainActivity
приложение вылетает. 

Что я делаю неправильно и как можно один раз инициализировать View для всех последующих
методов?

public class MainActivity extends Activity {    

public View customView = (View) findViewById(R.id.btn);

public void onCustomClick(View view) {
    switch (view.getId()) {
        case R.id.btn:
            customMethod(customView);
            break;
        // и т.д.

    }
}

private void customMethod (View customVar)
{
    // тело метода 
}
}

    


Ответы

Ответ 1



findViewById должно вызываться например в onCreate() методе активити. Т.е. оставьте public View customView; а в onCreate добавьте: customView = (View) findViewById(R.id.btn); UPD. Если копнуть глубже то в Activity у вас в самом начале метода onCreate() есть вызов setContentView(). Это именно этот вызов инициализирует разметку для Activity и только после того как он отработал можно вызывать findViewById(). UPD2. Диаграмма жизненного цикла Activity, спасибо @hardsky за идею, см. http://developer.android.com/training/basics/activity-lifecycle/starting.html Таким образом, исходя из диаграммы, провести инициализацию можно в: onCreate() после setContentView(), в методах onStart() и onResume().

Ответ 2



Ссылки на элементы разметки из .xml будут доступны после того , как этот .xml пройдет инфлейт (inflate) - преобразование из декларативного языка разметки в объекты виджетов. После такого преобразования мы можем получить ссылку на какой либо объект View по его id , заданному в разметке, с помощью метода findViewById(). Следует различать две разных реализации этого метода: Activity.findViewById() - данный метод будет возвращать ссылки на объекты виджетов из разметки только после применения метода активити setContentView() , который свяжет указанную в методе разметку с отображаемым на экране и проведет инфлейт этой разметки в объекты. View.findViewById() - данный метод вернет ссылку на объект виджета по его id , если виджет с таким id присутствует в View. При этом данный View может быть создан динамически - из кода, и тогда ему вообще не требуется инфлейт из XML-разметки , так как мы сразу получаем объекты виджетов, либо пройти инфлейт из XML-разметки отдельно от метода setContentView() , с помощью метода класса LayoutInflater - getLayoutInflater() и метода inflate(). Стоит заметить , что увидеть на экране полученные таким образом View можно будет только после того , как они будут добавлены в разметку , которая указана в методе setContentView(): //контейнер Layout1 присутствует в разметке , подключенной методом setContentView() LinearLayout layout = (LinearLayout) findViewById(R.id.Layout1); //контейнер view1 содержит TextView text1 и будет добавлен в контейнер Layout1 LayoutInflater inflater = getLayoutInflater(); View view1 = inflater.inflate(R.layout.view1, layout, false); TextView text = (TextView) view1.findViewById(R.id.text1); text.setText("Hello"); layout.addView(view1); view1 и text1 изначально не присутствуют в разметке , подключенной методом setContentView() , мы добавляем ее самостоятельно позже , так же устанавливаем значение одному из ее виджетов и только потом переносим в основную разметку , отображаемую на экране Для того, чтобы ссылка на объект (а View это тоже объект в глобальном понимании) была доступна для всех методов в пределах класса (а Activity это тоже класс , в глобальном понимании) необходимо сделать ссылку на этот объект полем класса: public class MainActivity extends Activity { View customView; ... } В коде к такому полю класса следует обращаться только по его имени , без указания класса: //Правильное обращение к полю класса. customView =(View) findViewById(R.id.customView); customView.setText("Hello"); //Неправильное обращение к полю класса. //Данный код создаст ЛОКАЛЬНУЮ переменную с именем, аналогичным имени поля класса , // но она никак не будет связана с полем класса View customView =(View) findViewById(R.id.customView); customView.setText("Hello");

воскресенье, 2 февраля 2020 г.

Чем опасна слепая инициализация?

#java #инициализация


Прошу объяснить, чем опасна "слепая инициализация" на каком-нибудь примере из практики? 

public class ShopActivity {
    private int revenue = Prefs.getRevenue();
}


Всегда ли нужно выносить код инициализации переменных в конструкторе? Когда можно
сделать исключение?
    


Ответы

Ответ 1



Компилятор переносит инициализацию полей в месте объявления (как и инициализацию полей в нестатических блоках) в конструктор (точнее говоря, во все имеющиеся). Инициализация полей при объявлении и инициализация полей в конструкторе – это практически одно и то же. Разница лишь в том, что: Инициализация полей в месте объявления происходит перед инициализацией полей в конструкторе. В случае инициализации полей в месте объявления невозможно перехватить исключение (при этом, если исключение checked, то нужно будет создать конструктор, который далее будет прокидывать возникшее исключение).

Ответ 2



Если совсем теоретически, то инициализируя свойства класса в момент их объявления необходимо быть твердо уверенным в том, что используемые для инициализации объекты существуют, а методы отработают корректно, так как вставить проверки и try/catch не получится. public class Test{ private Int number = 10; //тут вообще никаких проблем private String string = SomeStaticClass.getString(); //А если getString() бросит исключение? private Int number2 = Integer.parseInt(string); //А если string "кривая"? private View view = findViewById(R.id.view); //findViewById работает с инициализированным контекстом, которого на данном моменте просто не существует }

среда, 29 января 2020 г.

Что даёт инициализация массива одним элементом?

#cpp #массивы #инициализация


Был приведен пример из учебника.

Задача: К строка студентам обратились с просьбой оценить качество пищи в студенческом
кафетерии по 10 шкале. Поместить 40 ответов в массив целых чисел и просуммируйте результаты
опросов.

int main()
{

    setlocale(LC_ALL, "Russian");  

    const int respenseSize = 40, frequencySize = 11;  
    int responses[respenseSize] = { 1, 2, 6, 4, 8, 5, 9, 7, 8, 10, 1, 6, 3, 8, 6,
10, 3, 8, 2, 7, 6, 5, 7, 6, 8, 6, 7, 5, 6, 6, 5, 6, 7, 5, 6, 4, 8, 6, 8, 10 };  

    int frequency[frequencySize] = { 0 };//  ?

    for (int answer = 0; answer < respenseSize; answer++)
        ++frequency[responses[answer]];
    cout << "Рейтинг" << "       " << "Частота" << endl;

    for (int rating = 1; rating < frequencySize; rating++)
        cout << "     " << rating << "     " << frequency[rating] << endl;
    return 0;


}


Вопрос: int frequency[frequencySize] = { 0 };  мы обнулили первое начальное значение?для
того что бы на экране как мы запустили программу не было в столбике 0 0 или тут что
то более глубокое.Код весь ясен кроме этой части.Спасибо.
    


Ответы

Ответ 1



Имеется следующая цепочка из цитат из стандарта C++, которые объясняют, как инициализируются элементы массива при инициализации массива вида int frequency[frequencySize] = { 0 }; Первое, на что следует обратить внимание, это то, что массивы относятся к агрегатным типам. Поэтому обращаемся в раздел, где описана инициализация агрегатных типов 8.5.1 Aggregates: 7 If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from its brace-or-equal-initializer or, if there is no brace-or-equalinitializer, from an empty initializer list (8.5.4). Эта выдержка из стандарта говорит о том, что если инициализаторов меньше, чем элементов в массиве, то элементы массива, которые не имеют явного инициализатора инициализируются как будто бы указаны пустые фигурные скобки для каждого элемента. То есть это можно представить следующим образом int frequency[frequencySize] = { 0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} }; Теперь обратимся к стандарту за разъяснением, что означает, когда имеется пустые фигурные скобки. В параграфе №3 раздела 8.5.4 List-initialization, написано — Otherwise, if the initializer list has no elements, the object is value-initialized А что означает value-initialized? Об этом написано в разделе 8.5 Initializers стандарта 8 To value-initialize an object of type T means: — otherwise, the object is zero-initialized. И, наконец, понятие zero-initialized означает (8.5 Initializers) 6 To zero-initialize an object or reference of type T means: — if T is a scalar type (3.9), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T; То есть все элементы массива, которым не были заданы явным образом инициализаторы, инициализируются нулем. Поэтому в итоге данная запись int frequency[frequencySize] = { 0 }; эквивалентна следующей записи: int frequency[frequencySize] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; И, в итоге, получаем, что запись int frequency[frequencySize] = { 0 }; эквивалентна записи int frequency[frequencySize] = {}; Имейте в виду, что в языке C две последние записи не эквивалентны. Для языка C запись int frequency[frequencySize] = {}; ошибочна, и компилятор выдаст сообщение об ошибке.

Ответ 2



int a[N] = {0}; инициализирует первый элемент нулем, и обнуляет все остальные элементы массива. Если написать = {1};, то в массиве будет {1, 0, 0, ...}, если написать = {1, 2}; то в массиве будет {1, 2, 0, ...}, таким образом первые элементы принимают значения, указанные в {} (списке инициализации), а остальные элементы инициализируются значением по-умолчанию (нулем). Для обнуления всего массива достаточно написать = {};

суббота, 4 января 2020 г.

Инициализация вектора в конструкторе

#cpp #классы #конструктор #инициализация


class A {
public:
 A(size_t size):vector(size, default_value){}

private:
 std::vector vector;
 const int default_value = -1;
}


Почему вектор инициализируется рандомным значением? Константа ещё не успевает стать
-1? Как тогда сделать правильно?
    


Ответы

Ответ 1



Да, раз Вы проводите инициализацию в списке инициализации, то надо строчку с константой вынести выше, поскольку операция конструирования полей класса происходит в порядке из записи. У Вас еще неизвестно значение этой константы. Поэтому сделайте минимум так: class A { public: A(size_t size) : vector(size, default_value) {} private: сonst int default_value = -1; std::vector vector; }

Ответ 2



Члены класса инициализируются в порядке их объявления в классе. Поэтому для этого определения класса class A { public: A(size_t size):vector(size, default_value){} private: std::vector vector; const int default_value = -1; }; член данных vector инициализируется, когда член данных default_value еще не был инициализирован. Если переставить эти члены данных местами, то программа будет иметь ожидаемое поведение #include #include class A { public: A( size_t size ) : vector( size, default_value ) { } friend std::ostream & operator <<( std::ostream &, const A & ); private: const int default_value = -1; std::vector vector; }; std::ostream & operator <<( std::ostream &os, const A &a ) { for ( int x : a.vector ) std::cout << x << ' '; return os; } int main() { A a( 10 ); std::cout << a << std::endl; return 0; } Вывод программы на консоль -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 Из стандарта C++ (12.6.2 Initializing bases and members) 13 In a non-delegating constructor, initialization proceeds in the following order: ... Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers). Если бы вы объявили член данных default_value как статический член данных класса, то тогда инициализация нестатического члена данных vector не зависела бы от порялка объявления этих членов данных, так как статические члены класса инициализируются до создания любого объекта класса. class A { public: A( size_t size ) : vector( size, default_value ) { } friend std::ostream & operator <<( std::ostream &, const A & ); private: std::vector vector; static const int default_value = -1; };

четверг, 2 января 2020 г.

Переменная = empty

#cpp #инициализация #объявление


Можно ли в c++ новосозданной переменной присвоить значение empty?
    


Ответы

Ответ 1



В C++ 17 имеется класс std::optional - обертка вокруг других объектов, который позволяет определять, есть ли значение у переменной или нет. Однако многие компиляторы еще не поддерживают этот класс. Поэтому для фундаментальных типов нет такого значения, как empty. Вы можете переменные этих типов инициализировать нулем, как это делает компилятор для статических переменных. Тем не менее нельзя сказать, что такие значения являются значением empty.

Ответ 2



В дополнение к ответу выше, имеется еще boost::optional и boost::tribool. Простенькая реализация аналога optional пилится самостоятельно очень быстро.

вторник, 31 декабря 2019 г.

Скобки () {} при инициализации внутри класса

#cpp #классы #конструктор #инициализация #объявление


Подскажите пожалуйста, а есть ли разница между использованием круглых и фигурных
скобок при инициализации конструктора внутри класса. Оба варианта работают корректно.

class A
{
public:
    char c;
    int d;

    A(char ch)
        :c(ch) {}
    A(char ch, int i)
        :c{ch}, d{i} {}
};

int main()
{
    A first = A('a');
    A second = A('b', 1);

    cout << "First: "  << first.c << endl;
    cout << "Second: " << second.c  << ' ' << second.d << endl;

    return 0;
}

    


Ответы

Ответ 1



Начнем с того, что имеются случаи, когда нельзя применять инициализацию одного из видов к членам данных класса. Например, если у вас имеется член класса, который является агрегатом (структурой или массивом), то возможные виды инициализации ограничены. Рассмотрим несколько примеров. В данном примере внутри структуры A объявляется агрегатный член данных B, имеющий тип структуры. Тогда данное объявление конструктора будет некорректныым struct A { A(int x) : b( x ) {} struct B { int x; } b; }; int main() { A a( 10 ); } Компилятор выдаст сообщение об ошибке, говорящее о том, что он не может преобразовать объект типа int в объект типа struct B. Однако если вы замените круглые скобки на фигурные, struct A { A(int x) : b{ x } {} struct B { int x; } b; }; int main() { A a( 10 ); } то код будет успешно компилироваться, так как объект b будет инициализирован как агрегат. Теперь если в конструкторе заменить тип у параметра с int на A::B, то ситуация изменится. Данная программа будет успешно компилироваться struct A { struct B; A(B x) : b( x ) {} struct B { int x; } b; }; int main() { A::B b = { 10 }; A a( b ); } так как структуры, которые являются агрегатами, имеют конструктор копирования, создаваемый компилятором неявно. Иная ситуация складывается, когда членом данных, который представляет собой агрегат, является массив. Как и в случае со структурой, данная программа не будет компилироваться struct A { struct B; A(int x) : b( x ) {} int b[1]; }; int main() { int b = 10; A a( b ); } так как нет преобразования из целочисленного типа в массив. Однако если заменить параметр на массив, не важно, является он ссылкой или нет, как показано ниже struct A { struct B; A(int x[1]) : b( x ) {} int b[1]; }; int main() { int b[1] = { 10 }; A a( b ); } или struct A { struct B; A(int ( &x )[1]) : b( x ) {} int b[1]; }; int main() { int b[1] = { 10 }; A a( b ); } то программа не будет компилироваться, так как в первом случае нет преобразования из указателя в массив, а во втором случае, когда параметр объявлен как ссылка, массивы не имеют конструктора копирования. Для инициализации члена данных, который является массивом, можно использовать следующую запись struct A { struct B; A(int x) : b{ x } {} int b[1]; }; int main() { int b = 10; A a( b ); } Данная программа успешно скомпилируется. Однако вы не можете запись с фигурными скобками заключить еще в круглые собкки, как показано ниже struct A { struct B; A(int x) : b({ x }) {} int b[1]; }; int main() { int b = 10; A a( b ); } Компилятор выдаст сообщение об ошибке, так как, опять-таки, для массивов нет конструктора копирования. Однако для структур такая запись инициализации будет успешно воспринята компилятором, так как структуры, как агрегаты, имеют неявно объявленный компилятором конструктор копирования. struct A { A(int x) : b({ x }) {} struct B { int x; } b; }; int main() { int b = 10; A a( b ); } Данная программа успешно скомпилируется. Для арифметических типов инициализация с фигурными скобками не разрешает "сужение" значения, то есть использовать в качестве инициализатора значение, которое потенциально не может разместиться в инициализируемом объекте. Поэтому следующая программа не будет компилироваться struct A { A(int x) : b{ x } {} short b; }; int main() { int b = 10; A a( b ); } Причиной ошибки будет то, что объект типа short не в состоянии разместить все значения объекта типа int, то есть будет иметь место "сужение" инициализирующего значения. Однако если заменить фигурные скобки на круглые, то программа успешно скомпилируется struct A { A(int x) : b( x ) {} short b; }; int main() { int b = 10; A a( b ); } Когда инициализируемый член класса является определенный пользователем тип, то в дело вступают конструкторы, которые имеют параметр типа std::initializer_list Например, в приведенной ниже программе, когда нет такого конструктора, можно инициализировать член класса как b( x ), или как b{ x }, или даже как b( { x } ) struct A { A(int x) : b({ x }) {} struct B { B(int x) { std::cout << "B( int )" << std::endl; } } b; }; int main() { int b = 10; A a(b); } Однако если в классе присутствует конструктор с параметром типа std::initializer list, то для инициализаций вида b{ x } и b( { x } ) будет вызван именно он. А для инициализации вида b( x ) будет вызван другой конструктор. Например, для этой программы struct A { A(int x) : b( x ) {} struct B { B(int x) { std::cout << "B( int )" << std::endl; } B(std::initializer_list) { std::cout << "B( std::initializer_list )" << std::endl; } } b; }; int main() { int b = 10; A a(b); } будет выведено сообзение B( int ) А для этой программы struct A { A(int x) : b{ x } {} struct B { B(int x) { std::cout << "B( int )" << std::endl; } B(std::initializer_list) { std::cout << "B( std::initializer_list )" << std::endl; } } b; }; int main() { int b = 10; A a(b); } будет выведено сообщение B( std::initializer_list ) Вообще, эта тема достаточно обширная. О некоторых причудах инициализации я написал на своем сайте в конце темы Шутка - ложь, но в ней намек, добрым молодцам урок.

Ответ 2



Ну, например, напишите int main(int argc, const char * argv[]) { A a(300); A b{300}; } Вам сразу сообщат, что второе объявление не годится - поскольку 300 в char не поместится. Инициализация с фигурными скобками не разрешает сужающее преобразование. Есть и другие отличия - например, при фигурных скобках компилятор будет искать конструктор от initializer_list. Например, если в вашем классе будет такое: class A { vector v; ... A(int x):v{x} {} A(int x):v(x) {} то последние две строчки означают совершенно разные вещи - первая - вектор с элементом x, а вторая - вектор с x элементами...

пятница, 20 декабря 2019 г.

Почему инициализацию полей прописывают вне тела конструктора

#cpp #классы #конструктор #инициализация


Практически всегда в в примерах кода встречал инициализацию полей класса вне тела
конструктора:

MyClass(): a(1),b(2),c(3)
{ }


а не 

MyClass()
{
 a = 1;
 b = 2;
 c = 3;
}


Если оба варианта рабочие, в чем практическая польза от инициализации вне тела конструктора?
    


Ответы

Ответ 1



В первом случае у вас вызываются конструкторы для полей, и тем самым осуществляется инициализация полей.. Во втором случае у вас сначала вызываются конструкторы по умолчанию, и, если инициализаторы полей отсутствуют в их определении, то затем еще вызываются копирующие операторы присваивания, чтобы инициализировать соответствующим образом эти поля, что может быть в конечном итоге очень затратно. Представьте, например, что в конструкторе динамически выделяется память. Тогда затем в копирующем операторе присваивания эта память будет переопределяться. Кроме того может так оказаться, что какой-то член данных вообще не имеет конструктора по умолчанию, а только конструкторы с параметрами. В этом случае второй вариант вообще не будет компилироваться. Рассмотрите простой пример. Данная программа будет успешно компилироваться #include struct A { int x; A( int x ) : x( x ) {} }; struct B { A a; B() : a( 10 ) {} }; int main() { return 0; } Однако следующая программа компилироваться не будет #include struct A { int x; A( int x ) : x( x ) {} }; struct B { A a; B() { a = 10; } }; int main() { return 0; } так как отсутствует конструктор по умолчанию у класса A, который должен быть вызван до передачи управления управления в тело конструктора B

Ответ 2



В порядке вызова. Сначала поля класса инициализируется в списке инциализации (вариант 1). Если списка нет, то компилятор сделает его сам в том порядке, в котором поля объявлены в классе. Потом будет происходить то, что написано в теле конструктора (вариант 2). Для конкретно вашего примера разницы никакой, т.к. у вас используются простые типы данных. Дополнительных накладных расходов нет. Это имеет смысл для классов, которые в качестве полей имеют объекты других классов. Таким способом можно достичь несоздание объекта класса, если при инициализации какого-нибудь его поля произойдёт ошибка. Ну и для объектов других классов могут быть затраты на инициализацию объекта. Поэтому может получиться повторный вызов. Ну и последний аргумент - вариант 1 является хорошей практикой. Думаю, что-то ещё добавят в комментариях и других ответах.

пятница, 13 декабря 2019 г.

Инициализация нескольких переменных

#cpp #инициализация


Можно ли в C++ объявить несколько переменных и сразу присвоить одинаковое значение?
Пример:

int a, b, c, d = 0;


Чтобы все переменные равны были 0.
    


Ответы

Ответ 1



Объявить несколько переменных и затем присвоить им одно и тоже значение не составляет проблему. Вы можете написать, например int a, b, c, d; a = b = c = d = 0; Однако если вы хотите инициализировать переменные при их объявлении, то придется каждую переменную инициализировать отдельно. Например int a = 0, b = 0, c = 0, d = 0; или int a = 0, b = a, c = a, d = a; Простое объявление в C++ определяется следующим образом (7 Declarations) The simple-declaration attribute-specifier-seqopt decl-specifier-seqopt init-declarator-listopt; где init-declarator-list определяется как (8 Declarators) init-declarator-list: init-declarator init-declarator-list , init-declarator init-declarator: declarator initializeropt То есть каждый декларатор (для простоты: объявляемый идентификатор) в списке имеет свой собственный инициализатор.

Ответ 2



Можно использовать инициализацию из составного объекта, появившуюся в c++17: #include auto [a, b, c, d] = std::array(); Или в более общем случае для инициализации разными значениями: auto [a, b, c, d] = std::array{{ 1, 2, 3, 4 }}; Такой вариант с большой долей вероятности компилятором будет развернут в тот же код, что и при использовании явной ручной инициализации вида: int a = 1; int b = 2; int c = 3; int d = 4; Однако мне кажется проще и правильнее сразу использовать массив, а не индивидуальные разноимённые переменные.

Ответ 3



Еще можно как-то так поступить: struct vars { int a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p; vars() { ZeroMemory(this,sizeof(vars)); } };

воскресенье, 8 декабря 2019 г.

C++, более одного объявления в выражении инициализации цикла for

#cpp #for #инициализация #объявление #выражение


Существует ли в C++ способ объявить в выражении инициализации цикла for несколько
переменных разного типа?

Например:

for (size_t s = 0, float f = 0.f; ; ) {}

    


Ответы

Ответ 1



Начиная с С++17 у вас есть вариант с structured binding for (auto [s, f] = std::make_tuple(0, 0.0); ; ) { // Работаем с `s` и `f` } А до появления structured binding были лишь трюки вроде for (struct { size_t s = 0; float f = 0.f; } sf; ; ) { // Работаем с `sf.s` и `sf.f` } или for (struct { size_t s; float f; } sf = { 0 }; ; ) { // Работаем с `sf.s` и `sf.f` } Такой трюк не используется особенно широко, но тем не менее доступен со времен С.

воскресенье, 1 декабря 2019 г.

Инициализация неинициализированной переменной не приводит к UB?

#cpp #инициализация


Почему (1) является UB, а (2) - нет?

char x;
char y = x; // (1)

unsigned char x;
unsigned char y = x; // (2)


Информация взята отсюда
    


Ответы

Ответ 1



В настоящий момент имеется расхождение между C стандартом и C++ стандартом. В стандарте зыка C написано (6.2.6 Representations of types) 5 Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.50) Such a representation is called a trap representation. Так называемый символьный тип (character type) определяется в стандарте C следующим образом (6.2.5 Types): 15 The three types char, signed char, and unsigned char are collectively called the character types. The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char Интересно отметить, что в стандарте C++ нет определения термина character type, хотя этот термин используется, и под ним подразумевается аналогичное определение как обобщенное название типов char, signed char и unsigned char. Итак, в стандарте языка C говорится о неопределенном поведении, когда объект не имеет (любой) символьный тип. В стандарте C++ 2014, как я понимаю, при инициативе Страуструпа, решили уточнить в частности, и это положение о неопределенном поведении в отношении символьных типов. Там указали, что исключением является только беззнаковый символьный тип (unsigned char) 12 If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced (5.18). [Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2. — end note] If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases: .... (12.3) - If an indeterminate value of unsigned narrow character type is produced by the evaluation of the initialization expression when initializing an object of unsigned narrow character type, that object is initialized to an indeterminate value. Я думаю, что это уточнение в стандарте C++ 2014 связано с тем, что для некоторых систем представления целочисленных значений для отдельных аппаратных архитектур для знакового символьного типа также имеется в наличии так называемое trap-представление. Например, отрицательный ноль может быть таким представлением, не допустимым для представления валидных чисел.

суббота, 30 ноября 2019 г.

Можно ли как-нибудь упростить инициализацию классов на Python?

#python #python_3x #классы #конструктор #инициализация


У меня есть конструктор класса примерно вот такого вида:

class A:
    def __init__(self, a1, a2, ..., an):
        self.a1 = a1
        self.a2 = a2
        ...
        self.an = an
    ...


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

UPD. Дополню вопрос: 1) Как быть в случае, когда присутствуют значения по умолчанию?

class B:
    def __init__(self, b1=b1_0, b2=b2_0, ..., bn=bn_0):
        self.b1 = b1
        self.b2 = b2
        ...
        self.bn = bn
    ...


2) Возможно ответ последует из п.1, но всё равно задам ещё один вопрос: Что делать,
если для каких-то аргументов есть значение по умолчанию, а для каких-то нет?
    


Ответы

Ответ 1



Если неизменяемый объект хотите создать, представляющий запись с полями, можно использовать collections.namedtuple: from collections import namedtuple A = namedtuple("A", "a1 a2 an") A.__new__.__defaults__ = (0,) * 3 # set default values print(A(1, 2)) # -> A(a1=1, a2=2, an=0) Для изменяемых объектов, можно использовать types.SimpleNamespace: from types import SimpleNamespace print(SimpleNamespace(a1=1, a2=2, an=3)) # -> namespace(a1=1, a2=2, an=3) SimpleNamespace это простая альтернатива конструкции: class NS: pass, которая за вас __init__, __repr__, __eq__ методы определяет. Чтобы указать типы, в неизменяемом случае, можно использовать typing.NamedTuple: from typing import NamedTuple class B(NamedTuple): b1: str = '0' b2: int = 1 print(B()) # -> B(b1='0', b2=1) print(B(b2=3)) # -> B(b1='0', b2=3) PEP 557 -- Data Classes (Python 3.7+) хочет ввести классы данных для случая «изменяемые namedtuple со значениями по умолчанию». Для предыдущих версий Питона, можно использовать неинтегрированную в стандартную библиотеку реализацию из dataclasses пакета: from dataclasses import dataclass # $ pip install dataclasses @dataclass class B: b1: str = '0' b2: int = 1 print(B()) # -> B(b1='0',b2=1) b = B(b2=2) print(b) # -> B(b1='0',b2=2) b.b1 = '1' print(b) # -> B(b1='1',b2=2) В более общем случае, можно воспользоваться attrs пакетом, который множество дополнительной функциональности предоставляет. См. ещё примеры в ответе на похожий вопрос: Как создать коллекцию объектов пользовательского класса?

Ответ 2



Тогда уж так: class A: def __init__(self, **kwargs): self.__dict__.update(kwargs) kwargs - это словарь аргументов (пары "имя": значение). Вариант со значениями по-умолчанию: class A: __defaults = dict(b1=1, b2='2', foo='bar') def __init__(self, **kwargs): self.__dict__.update(self.__defaults) self.__dict__.update(kwargs) Первоначально класс инициализируется значениями по-умолчанию (__defaults), а потом значениями из kwargs. Если в kwargs есть аргументы, присутствующие в __defaults, то новые значения (из kwargs) заменят старые (из __defaults).

Ответ 3



Мне больше нравится следующий метод: class A: def __init__(self, a1, a2, ..., an): self.__dict__.update({k: v for k, v in locals().items() if k != 'self'}) Немного теории: __dict__ хранит определенные пользователем атрибуты объекта, в нашем случае мы ручками добавляем в него новые значения, он представляет из себя словарь, где ключ это имя атрибута, а значение - значение соответствующего атрибута locals() возвращает локальные переменные, в нашем случае - аргументы __init__ self фильтруем, его нам задавать не нужно

Ответ 4



class A: def __init__(self, a1, a2, ..., an): for i in self.__init__.__code__.co_varnames[1:-1]: exec('self.{0} = {0}'.format(i)) Идея в чем: получаем список всех аргументов __init__, потом в цикле, исключая self, присваиваем каждый аргумент атрибуту объекта с таким же названием.

пятница, 14 июня 2019 г.

Инициализация переменной в конструкторе

Есть такой класс:
public class Game implements Screen {
private World world; private Renderer renderer;
public Game() { world = new World(); renderer = new Renderer(world); } }
Почему переменные инициализируются внутри конструктора, а не в месте их создания?
Например, можно просто написать:
private World world = new World();
В чем отличие таких способов инициализации?


Ответ

Плюсы использования инициализации переменной в строке с её объявлением:
Если имеется несколько конструкторов, то это позволяет избежать необходимости инициализировать переменную в каждом из конструкторов. При использовании анонимных классов нет возможности создавать их конструкторы - соответственно, инициализировать final поля класса можно только с помощью инициализации при объявлении и блока инициализации объекта.
Минусы такого использования:
Отсутствует возможность обработки исключений при инициализации. Отсутствует возможность проведения сколько-либо сложных вычислений (например, использование цикла for) при инициализации. В этом случае придётся воспользоваться конструктором или блоком инициализации объекта.

вторник, 4 июня 2019 г.

Присвоение значения переменной при объявлении в классе

Есть класс:
class A { public: static int count;
A() { count++; // При создании каждого объекта эта переменная должна увеличиваться } };
Надо присвоить значение этой переменной 0, до вызова конструктора. Но если это сделать вот так:
static int count = 0;
То компилятор ругается. Как обойтись в такой ситуации?


Ответ

Просто надо правильно определить эту переменную вне класса
class A { public: static int count;
A() { count++; // При создании каждого объекта эта переменная должна увеличиваться } };
//... int A::count = 0;
Вы также можете опустить инициализатор, так как переменная в любом случае будет инициализирована нулем:
int A::count;
Если определение класса помещено в заголовочный файл, то определение статической переменной нужно поместить в один из программных модулей.

вторник, 28 мая 2019 г.

Компилятор не поддерживает расширенную инициализацию

Имеется такой код:
int main() { double d {4.5}; int x {d}; }
Прочитал, что в новой версии С++11 есть поддержка на запрет сужающих преобразований при инициализации. Но компилятор ошибки не выдает, хотя должен (ибо я пытаюсь double в int преобразовать с синтаксисом новой версии инициализации (с помощью фигурных скобок), которая как раз запрещает подобные небезопасные преобразования.
IDE - DevC++ 5.11 Компилятор - TDM-GCC 4.9.2.
Вопрос: Так почему нет поддержки этой новой инициализации? Компилятор новый же, его вместе со средой скачал недавно вот.


Ответ

Стандарт С++ не обязывает реализацию языка выдавать именно "ошибку компиляции" для случаев когда в стандарте написано что код ошибочен. (Однако при этом не должно ломается SFINAE, иначе это уже баг компилятора).
Компилятор g++ в некоторых случаях ограничивается предупреждением:
double d = 4.5; int x{d}; // предупреждение в g++ int y{4.5}; // ошибка в g++
Впрочем этого достаточно, т.к. рекомендуется компилировать код с -Wall -pedantic -Werror
Что касается стандарта, то int x{d}; это несомненно неправильный код. В разделе [dcl.init.list] есть аналогичный пример

воскресенье, 14 апреля 2019 г.

Почему статический блок инициализации выполнился после конструктора?

Доброго времени суток коллеги!
В литературе пишут, что при загрузке класса в первую очередь выполняется статический блок, а потом уже динамический блок и конструктор. У меня получилось написать класс так, что статический блок выполнился в последнюю очередь.
public class Singleton { static Singleton st = new Singleton(); static { //статический блок System.out.println("Статический блок"); }
{ //анонимный блок System.out.println("Динамический блок"); }
Singleton(){ //конструктор System.out.println("Конструктор");
} public static void main(String[] args) { //Пустая точка входа } }
OUTPUT:
Динамический блок Конструктор Статический блок
Вопрос: Как объяснить наблюдаемое поведение?


Ответ

Давайте по порядку посмотрим:
Первым у вас объявлено статическое поле:
static Singleton st = new Singleton();
Его нужно проинициализировать, соответственно вызывается конструктор:
Singleton() { System.out.println("Конструктор"); }
Но перед вызовом конструктора есть динамический блок инициализации, поэтому сперва инициализация объекта происходит в этом блоке, а потом уже выполняется конструктор:
{ //анонимный блок System.out.println("Динамический блок"); }
//затем вызывается непосредственно конструктор
Итак, первая строчка класса готова, статическое поле static Singleton st проинициализировано, далее уже инициализируем статический блок. Можете, на основе моего ответа предположить, что будет если в статический блок прописать ещё один вызов конструктора Singleton? Предположите, и можете попробовать затем и сравнить своё предположение.
UPD.
Нельзя сказать что static блок вызывается перед динамическим блоком. Они вызываются при разных условиях. Static блок вызывается после загрузки класса class loader'oм, а динамический блок вызывается при создании экземпляра класса. То есть static - инициализация класса, dynamic - инициализация экземпляра класса. И динамический блок в вашем конкретном примере вызывается во время инициализиции экземляра Singleton в первой строчке, которая static поле.