Страницы

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

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

вторник, 17 марта 2020 г.

Конструкторы и операторы копирования

#конструктор #cpp


Могу ли я записать конструктор копирования через оператор присваивания?
Например вот так:
A::A(const A * a) {
    *this = a;
}

A &A::operator=(const A * a) {
    delete str;
    str = new char[strlen(a->str)+1];
    strcpy(str, a->str);
    return *this;

}

Еще хочется уточнить, правильно ли если я делаю A * a в примере выше, потому примеры
с разных сайтов A & a. C этого всплывает вопрос, что должен возвращать оператор присваивание:
A &A::operator=(const A * a)

или 
A * A::operator=(const A * a)

Вообщем правильный ли мой код выше?
Объект класса создается так A * a = new A();    


Ответы

Ответ 1



Конструктор копирования можно написать через оператор присваивания, не вопрос. Только то, что вы пишете — это не конструктор копирования и не оператор присваивания. Для них аргументом является экземпляр класса, а не указатель на него. Код неправильный ещё и по другой причине: вы не учитываете случай копирования объекта в себя. Сигнатура конструктора копирования обязана быть A(A& a) или A(const A& a) (есть ещё немного экзотических возможностей, вот документация), всё остальное, в частности, то, что у вас — не конструктор копирования. Сигнатура оператора копирующего присваивания (а начиная с C++11 возможен и перемещающий) обязана быть A& operator= (A a) или A& operator= (const A& a) (документация), всё остальное — не оператор копирующего присваивания. В частности, у вас просто конструктор из стороннего типа, и оператор присваивания значения стороннего типа. Указатель на A для A — сторонний тип.

пятница, 13 марта 2020 г.

Конструкторы в Java

#java #конструктор


Здравствуйте, скажите, пожалуйста, какя разница между конструкторами:

int age;
string name;
Man (int age, string name){
  age=age;
  name=name;
}

Man (int age, string name){
  this.age=age;
  this.name=name;
}

    


Ответы

Ответ 1



В первом вы не установите значение у полей класса. Это называется сокрытие переменных(Variable shadowing). Хороший ответ о сокрытии на SOen.

среда, 4 марта 2020 г.

С++ аварийный выход из конструктора

#cpp #исключения #конструктор


Можно ли во время создания объекта, при сбое в конструкторе завершить его без создания
объекта и оповестить об этом программу?  

serialPort::serialPort(LPSTR serialName)
{
    HANDLE  serialDesc;

    // COM openning
        serialDesc = CreateFile(serialName, GENERIC_READ | GENERIC_WRITE,
                                0, NULL, OPEN_EXISTING, 0, NULL);
        if (serialDesc == INVALID_HANDLE_VALUE)
        {
             // сделать что-то, что бы объект не создавался и оповестить программу
        }
}


Мне в голову приходят только передача указателя на внешнюю структуру с последующей
проверкой, или содержать дополнительную переменную в классе, которую так же придется
проверять извне.
Еще прочитал про try-throw-catch, но не могу найти явную информацию как он работает
в конструкторе.
    


Ответы

Ответ 1



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

Ответ 2



я обязан их закрыть в ручную, иначе они будут считаться открытыми, а их дескрипторы утеряны? закрывать обязательно нужно, так как во первых будет потеря ресурсов - файловых дескрипторов + памяти. Если ваша программа работает 24х7х365, то если эти объекты создаются постоянно, то ваша программа может аварийно завершиться. Вопрос как? Если вы сгенерируете исключение в конструкторе, а файл - член этого класса? Стандартный подход 1) в конструкторе не делать инициализую переменных (например просто их нулить) а делать это в отдельном методе, например init, которое может сгенерировать исключение, которое может быть обработано сверху.В этом подходе, у вас будет вызван деструктор, который будет освобождать ресурсы 2) Использовать наследование 3) Использовать умные указатели для членов класса, тут все понятно Пример 1 решения class Foo { FILE* f; public: Foo() { f = 0; } virtual ~Foo() { if ( 0 != f) fclose(f); } bool init() { f = fopen("file.txt", "+rw"); if ( !f ) { throw 1; } fseek(f, 0, SEEK_END); size_t fileSize = ftell(f); if (fileSize < 100) return false; return true; } }; int main() { { Foo f; try { f.init(); } catch (int& e) { // Обработка исключения, о ресурсах не беспокоимся, деструктор // класса Foo все сделает } } } Второй способ основан на том, чтобы поместить f в базовый класс и и там освобождать ресурсы ( при исключении в конструкторе порожденного класса, вызовется и деструктор базового) Пример 2 Решения Перенесем метод инит в конструктор порожденного класса class Foo { FILE* f; public: Foo() { f = 0; } virtual ~Foo() { if ( 0 != f) fclose(f); } }; class Delivery: public Foo { bool fileIsBig; public: Delivery(const char* fileName) { fileIsBig = true; f = fopen(fileName, "+rw"); if ( !f ) { throw 1; } fseek(f, 0, SEEK_END); size_t fileSize = ftell(f); if (fileSize < 100) fileIsBig = false; } virtual ~Delivery() { } }; int main() { try { Delivery d("myfile"); } catch (int& e) { // Обработка исключения из конструктора, ресурсы будут освобождены } }

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

Конструктор <vector>

#cpp #классы #перегрузка_операторов #конструктор #вектор


Хочу написать конструктор класса, который в качестве параметра принимает статический
массив (указатель на 1 элемент) и инициализирует вектор внутри класса этим массивом:

class A
{ 
  public:
    explicit A(int x[]):v(x,x+sizeof(x)/sizeof(*x)) {} // или (int* x)
  private:
    vector v;
};


Не работает... В чём проблема?
    


Ответы

Ответ 1



Параметр вашего конструктора имеет тип int *. Поэтому выражение sizeof(x)/sizeof(*x) эквивалентно выражению sizeof( int * )/sizeof( int ) И может равняться либо 2, если размер указателя равен, например, 8 байтам, а размер объекта типа int равен 4 байтам, либо 1, если данные размеры совпадают. Лучше объявить два перегруженных конструктора. Первый конструктор может быть определен как A( const int *first, const int *last ) : v( first, last ) {} а второй конструктор может быть определен либо как A( const int *first, size_t n ) : v( first, first + n ) {} либо использовать делегирующий конструктор A( const int *first, size_t n ) : A( first, first + n ) {} Также вы можете объявить шаблонный конструктор, как, например, template A( const int ( &a )[N] ) : v( a, a + N ) {} Вот демонстрационная программа, в которой показаны все три способа вызова конструкторов класса #include #include struct A { A( const int *first, const int *last ) : v( first, last ) {} A( const int *first, size_t n ) : v( first, first + n ) {} template explicit A( const int ( &a )[N] ) : v( a, a + N ) {} std::vector v; }; int main() { int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; A a1( a, a + sizeof( a ) / sizeof( *a ) ); for ( int x : a1.v ) std::cout << x << ' '; std::cout << std::endl; A a2( a, sizeof( a ) / sizeof( *a ) ); for ( int x : a1.v ) std::cout << x << ' '; std::cout << std::endl; A a3( a ); for ( int x : a1.v ) std::cout << x << ' '; std::cout << std::endl; } Ее вывод на консоль 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

Ответ 2



В параметрах функции, в отличие от объявлений массива, int x[] вырождается в int* x, т.е. ничем не отличается от обычного указателя. (Параметры int x[5] или int x[6] также вырождаются int* x). Поэтому надо использовать шаблон и ссылку на массив. class A { public: template explicit A(int (&x)[N]) : v(x, x + N) {} private: vector v; }; Ссылка int (&x)[N] не теряет количество элементов в массиве.

среда, 5 февраля 2020 г.

Перегрузка оператора +=

#cpp #строки #классы #перегрузка_операторов #конструктор


// Реализовать класс String  для работы со строками. Перегрузить операторы = + +=
- > < [] (подстрока) ++ --
#pragma once
#include 
using namespace std;

class String
{


    char* s;
    int length;

public:


    String(const char* str="")
    {
        this->length = strlen(str);
        this->s = new char[this->length+1];
        for (int i = 0; i < length; i++)
        this->s[i] = str[i];
    }


    String(const String& s1)
    {
        this->length = s1.length;
        this->s = new char[this->length];
        for (int i = 0; i < this->length; i++)
            this->s[i] = s1.s[i];
    }


    ~String()
    {
         delete[] s;
    }



String& String::operator=(String& right);
    String String::operator+(String right);
    //String& String::operator+=(const String& right);
    friend String& operator+=(String& left, const String& right);
    void String::show();

};


А это цпп файл: 

#include "String.h"

void String::show()
{
    for (int i = 0; i < this->length; i++)
        cout << this->s[i];
}


String& String::operator=(String& right)
{
    delete[] this->s;
    this->length = right.length;
    this->s = new char[this->length];
    for (int i = 0; i < this->length; i++)
        this->s[i] = right.s[i];
    return *this;
}

String String::operator+(String right)
{
    String new_string;
    new_string.length = this->length + right.length;
    new_string.s =new char[this->length + right.length+1];
    for (int i = 0; i < this->length; i++)
        new_string.s[i] = this->s[i];
    for (int i = 0; i < right.length; i++)
        new_string.s[this->length + i] = right.s[i];
    return new_string;
}

String& operator+=(String& left, const String& right)
{
    return left+right;
}


Почему у меня s3 всё равно при выводе пустая? Помогите начинающему!

#include "String.h"

void main()
{
    String s2("Hello");
    String s3;
    s3 += s2;
    s3.show();
    system("pause");
}

    


Ответы

Ответ 1



Мы, начинающие программисты, должны помогать друг другу.:) Начнем с того, что вам следует включить заголовок #include так как вы используете функции, объявленные в этом заголовке. Длину строки лучше определять, как имеющую тип size_t вместо int. class String { char* s; size_t length; //... Ваши конструкторы неверно копируют строки. Они не копируют в результирующую строку завершающий символ нуля '\0'. Например, String(const char* str="") { this->length = strlen(str); this->s = new char[this->length+1]; for (int i = 0; i < length; i++) // символ нуля не входи в диапазон `[0, length)` this->s[i] = str[i]; } Иначе непонятно, зачем вы выделяете память длиною this->length+1 вместо this->length. Непонятно, зачем вы вообще используете циклы для копирования строк, когда вы уже используете стандартные C функции из заголовка Лучше было бы объявить конструктор следующим образом String( const char *str = "" ) { this->length = std::strlen( str ); this->s = new char[ this->length + 1 ]; std::strcpy( this->s, str ); } То же самое замечание справедливо и для конструктора копирования. В определении класса нельзя указывать квалифицированное имя функций-членов класса при их объявлении. Хотя MS VC++ это допускает, но это является его собственным расширением языка, которое не соответствует стандарту. String& String::operator=(String& right); ^^^^^^^^ Эти объявления String String::operator+(String right); friend String& operator+=(String& left, const String& right); лучше переопределить следующим образом friend String operator + ( const String &left, const String &right ); String & operator += ( const String &right ); Функцию show лучше объявить как std::ostream & String::show( std::ostream &os = std::cout ) const; Переопределите соответствующие функции с учетом выше приведенных замечаний. Что касается копирующего оператора присваивания, то он может быть определен следующим образом String & String::operator =( const String &right ) { if ( this != &right ) { size_t n = std::strlen( right.s ); char tmp = new char[ n + 1 ]; std::strcpy( tmp, right.s ); delete [] this->s; this->s = tmp; this->length = n; } return *this; } Аналогичным образом вам нужно определить оператор String & operator += ( const String &right ); Например String & String::operator +=( const String &right ) { size_t n = this->length + std::strlen( right.s ); char tmp = new char[ n + 1 ]; std::strcpy( tmp, this->s ); std::strcat( tmp, right.s ); delete [] this->s; this->s = tmp; this->length = n; return *this; } Функция show определяется просто std::ostream & String::show( std::ostream &os ) const { return os << this->s; } Что касается вашего вопроса Почему у меня s3 всё равно при выводе пустая? то этот оператор String& operator+=(String& left, const String& right) { return left+right; } не изменяет левый аргумент. И более того имеет неопределенное поведение, так как возвращает ссылку на временный объект, создаваемый выражением left+right. EDIT: Если оператор operator [], который, как я понимаю, вы также должны определить, позволяет вставлять в объект класса символ '\0', то вам следует изменить определения конструктора копирования, копирующего опреатора присваивания, оператора operator +=, и дружественной функции operator +, заменив стандартные C функции strXXX на memXXX. Ниже приведена демонстрационная программа, которая показывает, как это можно сделать на примере некоторых членов класса. Другие члены класса постарайтесь определить сами. #include #include class String { private: char *s; size_t length; public: String( const char *str = "" ) { this->length = std::strlen( str ); this->s = new char[ this->length + 1 ]; std::strcpy( this->s, str ); } String( const String &src ) { this->length = src.length; this->s = new char[ this->length + 1 ]; std::memcpy( this->s, src.s, this->length + 1 ); } String & operator =( const String &src ) { if ( this !=+ &src ) { char *tmp = new char[ src.length + 1 ]; std::memcpy( tmp, src.s, src.length + 1 ); delete [] this->s; this->s = tmp; this->length = src.length; } return *this; } String & operator +=( const String &src ) { size_t n = this->length + src.length; char *tmp = new char[ n + 1 ]; std::memcpy( tmp, this->s, this->length ); std::memcpy( tmp + this->length, src.s, src.length + 1 ); delete [] this->s; this->s = tmp; this->length = n; return *this; } ~String() { delete [] this->s; } std::ostream & show( std::ostream &os = std::cout ) { return os << this->s; } }; int main() { String s1( "Hello world!" ); String s2; s2 = s1; s2.show() << std::endl; s2 += " Glad to see you!"; s2.show() << std::endl; return 0; } Вывод программы будет следующим Hello world! Hello world! Glad to see you!

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

Инициализация массива с помощью конструктора Java

#java #конструктор


Необходимо реализовать пузырьковую сортировку для массива. При этом массив инициализируется
с помощью:


пустого конструктора, при вызове которого генерируется массив с random значениями
конструктора, который на вход принимает длину и массив


Метод для генерации рэндом массива:

 public static int[] arrayCreator() {
    int arr[] = new int[5];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = (int) ((Math.random() * 5));
    }
    return arr;
} 


public void arrayPrint(int arr[]) {
    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }
}


Пузырьковая сортировка:

public void bubbleSort(int arr[]) {
    for (int i = 0; i < arr.length; i++) {
        for (int j = 0; j < arr.length - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int t = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = t;
            }
        }
    }
}


Класс и конструтор: 

public class Starter {

    private static int arr[];

    public static void main(String[] args) {
        int array[] = {1, 9, 6, -2, 12};
        Starter starter = new Starter();
        starter.arrayPrint(arr);
        starter.bubbleSort(arr);
        System.out.println("Array after sorting: ");
        starter.arrayPrint(arr);    
    }

    Starter() {
        int arr[] = arrayCreator();
    }

    Starter(int length, int array[]) {
        int arr[] = array;
    }


При создании обьекта starter конструктор не вызывается, массив не инициализуется.
Дальше NullPointerException.
    


Ответы

Ответ 1



В конструкторах класса вы инициализируете локальные массивы вместо массива - члена класса. Starter() { int arr[] = arrayCreator(); ^^^^^^^^^ } Starter(int length, int array[]) { int arr[] = array; ^^^^^^^^^ } Вам надо инициализировать член класса, а не объявлять в конструкторах локальные массивы. Также это плохая идея делать массив статическим членом класса. Каждый объект класса должен иметь свой член класса в виде массива. Кроме того непонятно, почему вы используете магическое число 5 в функции генерации массива со случайными значениями public static int[] arrayCreator() { int arr[] = new int[5]; ^^^^^^^^^^ for (int i = 0; i < arr.length; i++) { arr[i] = (int) ((Math.random() * 5)); } return arr; } Было бы более логично, если бы в эту функцию передавался аргумент, который указывал бы, какова должна быть размерность массива. Например, public static int[] arrayCreator( int n ) { int arr[] = new int[n]; for (int i = 0; i < arr.length; i++) { arr[i] = (int) ((Math.random() * 5)); } return arr; } Также если эти функции создания массивов используются только в конструкторах, то лучше объявить их с классом доступа private. Дополнительно можно написать еще одну public функцию типа reset, которая устанавливает новые случайные значения для элементов массива. Также совершенно не ясно, почему не статическая функция член класса arrayPrint имеет параметр тогда, как она должна распечатывать массив своего объекта public void arrayPrint(int arr[]) { ^^^^^^^^^^^^^^^^^^^^^ for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } лучше было бы определить ее как public void arrayPrint() { ^^^^^^^^^^^^ for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } и соответственно вызывать без аргумента. Обратите внимание, что объявленный в функции main массив int array[] = {1, 9, 6, -2, 12}; нигде не используется. Что касается вашей функции пузырьковой сортировки, то она будет выполнять циклы и проход по всему массиву, даже если массив уже отсортирован. Вы можете посмотреть ответ на аналогичный вопрос, чтобы сделать сортировку более эффективной. Хотя в том вопросе требуется отсортировать массив по убыванию, тем не мене это только сказывается на том, какой оператор логического отношения будет использовать. А в целом показан алгоритм сортировки, уменьшающий число проходов по массиву.

Ответ 2



Конструктор вызывается, но в нём вы инициализируете локальную переменную. int arr[] = arrayCreator(); запоминает созданный массив в локальной переменной, которая исчезает после того, как конструктор отработал. Пишите так: arr = arrayCreator(); Да, и arr не стоит объявлять как static, даже ваш класс создаётся всего лишь один раз. Всё же это данные, относящиеся к экземпляру класса.

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

#cpp #ооп #конструктор #деструктор


Заинтересовал вопрос: а почему порядок уничтожения данных объекта должен быть обратным
к порядку их конструирования? Нельзя ли удалять содержимое объекта в другой последовательности,
отличной от порядка их конструирования? Не связано ли это как-то с выравниванием данных
или с хранением ссылок на функции-члены (поправьте, если выражаюсь некорректно)? Если
есть возможность, предоставьте ссылку на источник. Благодарю!
    


Ответы

Ответ 1



Порядок удаления обратен порядку конструирования по вполне естественным причинам: в процессе конструирования и/или удаления между разными объектами могут существовать взаимозависимости. Если объект А во время своего конструирования полагался на то, что объект Б уже полностью сконструирован, то в процессе деструкции объекта A желательно было бы гарантировать объекту А, что объект Б ещё не деструктирован. Из этого естественным образом вытекает обратный порядок удаления. В случае, когда речь идет о подобъектах объекта класс-типа такая гарантия выглядит более чем естественно: в теле конструктора/деструктора классу, разумеется, нужно, чтобы его подобъекты были уже/еще работоспособны. В более общем случае (например, взаимный порядок создания/удаления разных статических объектов) аналогичные зависимости между разными объектами могут формироваться во время выполнения, то есть никаких шансов предсказать их наличие или доказать их отсутствие у компилятора нет. Поэтому разумным решением видится гарантировать глобальную упорядоченность настолько, насколько это возможно.

Ответ 2



В плюсах есть такое понятие как исключение. При не плановом возврате из функций остаётся не разобранный стек объектов. При очистке стека нужно вызывать деструкторы локальных переменных. Эти деструкторы будут вызываться по порядку декларации в программе и исключение не имеет информации удалён ли объект до этого. По-этому чтобы всё правильно очистилось пишется данная рекомендация. Стандарт : As control passes from a throw-expression to a handler, destructors are invoked for all automatic objects constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction. An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object. Перевод: По мере того, как управление переходит от throw-выражения к обработчику, деструкторы вызываются для всех автоматических объектов, созданных с момента ввода блока try. Автоматические объекты уничтожаются в порядке, обратном завершению их строительства. У объекта любой продолжительности хранения, инициализация или уничтожение которого завершается исключением, будут деструкторы, выполняемые для всех его полностью построенных подобъектов (исключая альтернативные члены объединяющего класса), то есть для подобъектов, для которых главный конструктор ( 12.6.2) завершил выполнение, а деструктор еще не начал выполнение. Точно так же, если не делегирующий конструктор для объекта завершил выполнение и делегирующий конструктор для этого объекта завершается с исключением, будет вызван деструктор объекта. Если объект был выделен в новом выражении, соответствующая функция освобождения (3.7.4.2, 5.3.4, 12.5), если таковая имеется, вызывается для освобождения памяти, занятой объектом.

Ответ 3



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

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

Вызов конструктора без аргументов?

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


Здравствуйте. Решаю 2 задачу 9 главы из книги Р. Лафоре. Наткнулся на проблему.
Задача:
Вспомните пример STRCONV из главы 8. Класс String в этом примере имеет дефект: у
него нет защиты на тот случай, если его объекты будут инициализированы слишком длинной
строкой (константа size имеет значение 15). Например: String s = "Эта строка имеет
очень большую длину и мы можем быть уверены, что она не уместится в отведенный буфер,
что приведет к непредсказуемым последствиям."; будет причиной переполнения массива
str строкой s с непредсказуемыми последствиями вплоть до краха системы. Создадим класс
Pstring, производный от класса String, в котором предотвратим возможность переполнения
буфера при определении слишком длинной строковой константы. Новый конструктор производного
класса будет копировать в str только size-1 символов, если строка окажется слишком
длинной, и будет копировать строку полностью, если она будет иметь длину меньшую, чем
size. Напишите функцию main() программы для проверки ее работы со строками разной длины.

#include 
#include 

const int size = 15;

class String {
protected:
    char str[size];
public:
    String () {
        str[0] = '\x0';
    }
    String (const char* s) {
        strcpy(str, s);
        std::cout << "Скопировано: " << str << std::endl;
    }
    void display () const {
        std::cout << str << std::endl;
    }
    operator char* () {
        return str;
    }
};

class Pstring : public String {
public:
    Pstring (char* s) {
        if (strlen(s) >= size) {
            for (int index = 0; index < size - 1; ++index) {
                str[index] = s[index];
            }
            str[strlen(str)] = '\x0';
        }
        else {
            String(s); // почему не копируется строка, а вызывается конструктор без
аргументов?
            }
    }
};

int main () {
Pstring p1 = "pointers is evil!!!";
p1.display ();
Pstring p2 = "char * is bad";
p2.display();

return 0;
}


При определении переменных класса Pstring вначале вызывается конструктор по умолчанию
String::String(); до выполнения блока. Далее, идёт проверка на длину строки. Вопрос:
Почему, если строка меньшего размера, то  при вызове конструктора String(s); в ветви
else не копируется значение строки? Почему на выводе пустая строка.
    


Ответы

Ответ 1



У вас имеется несколько проблем с определением конструкторов в классе Pstring. Во-первых, желательно, чтобы он вел себя как класс String, что означает, что он также должен иметь конструктор по умолчанию. Pstring() {} Во-вторых, вами определенный конструктор с параметром полностью неверный. В этом предложении конструктора str[strlen(str)] = '\x0'; присутствуют сразу же две ошибки. Строка str не имеет пока еще завершающего нуля, поэтому вызов strlen( str ) ведет к неопределенному поведению. К тому же вы должны установить в 0 символ в позиции size - 1 В этом предложении String(s); У вас создается временный объект, который сразу же удаляется в конце предложения. Он никак не связан с тем объектом, в конструкторе которого вызывается это предложение. Имейте в виду, что когда вы имеете дело со строками, а тем более со стандартными C функциями, предназначенными для работы со строками, то лучше в качестве индекса в строках использовать тип size_t вместо типа int. Также в качестве параметра конструктора следует задать параметр с типом const char *, так как, как по крайней мере следует из вашей программы, конструктор может иметь дело со строковыми литералами в качестве аргументов, а строковые литералы в C++ в выражениях преобразуются (за редким исключением) к типу const char *. Далее длина члена класса str - это внутреннее свойство самого класса. Поэтому его длина не должна быть глобальной переменной. И, наконец, желательно, чтобы деструктор был виртуальным. Я бы определили данные классы следующим образом class String { protected: static const size_t size = 15; char str[size]; public: String () { str[0] = '\x0'; } String (const char* s) { std::strcpy(str, s); std::cout << "Скопировано: " << str << std::endl; } virtual ~String() {} void display () const { std::cout << str << std::endl; } operator const char* () const { return str; } }; class Pstring : public String { public: Pstring() {} Pstring ( const char* s) { if ( std::strlen(s) < size) { std::strcpy( str, s ); } else { size_t i = 0; for ( ; i < size - 1; ++i) { str[i] = s[index]; } str[i] = '\x0'; // или просто //std::strncpy( str, s, size - 1 ); // str[size-1] = '\0'; } } };

Ответ 2



У вас здесь String(s); просто объявление переменной s типа String. Естественно, для нее вызывается конструктор по умолчанию. Вы не можете просто взять и вызвать конструктор посреди другой функции, как обычную функцию. Перепишите свой конструктор как Pstring(const char* s) { if (s) { strncpy(str,s,size-1); str[size-1] = 0; } } При s==nullptr я ничего не делаю, потому что конструктор базового класса по умолчанию уже зануляет первый символ. Кстати, рекомендовал бы и в String делать соответствующую проверку.

Не выполняется конструктор

#java #конструктор


Файл CleanBackupFolders.java:

import java.io.*;

public class CleanBackupFolders {
    public static void main(String[] args) throws IOException {
        ...
        Settings IniFileObj = new Settings();
        ...
    }
}


Файл Settings.java:

public class Settings {
    public void Settings() {
        System.out.println("Constructor executed!");
    }
}


В первом классе не выполняется вызов конструктора для объекта. Если же после поставить
строку

IniFileObj.Settings();


то все, что в этом конструкторе, исполняется. В чем проблема?
    


Ответы

Ответ 1



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

В чем отличие & от && на примере конструктора копирования и конструктора переноса?

#cpp #cpp11 #конструктор #семантика


Допустим, у нас есть класс с обычным конструктором:

   class SomeClass { 
    int size; 
    int *element; 
   public: 
    SomeClass(int i) { //обычный конструктор
     size = i; 
     element = new int[size]; 
     } 
    }; 


И предположим, что я добавил туда конструктор копирования и конструктор перемещения
(переноса):

SomeClass( SomeClass &&other ) { //конструктор перемещения
 size = other.size; 
 element = other.element; 
 other.size = 0; 
 other.element = nullptr; 
} 

SomeClass( SomeClass &other ) { //конструктор копирования
 size = other.size; 
 element = other.element; 
 other.size = 0; 
 other.element = nullptr; 
} 

int main()  {
 SomeClass a1(5);
 SomeClass a2(a1); //вызов конструктора копирования
 SomeClass a3 = move(a1); //вызов конструктора переноса
}


Здесь один нюанс: конструктор копирования не будет копировать, он тоже будет выполнять
перенос.

В чем тогда отличие? Если фактически происходит одно и тоже. Code::Blocks выдает
разницу в секунду. Думаю, тогда вопрос встает между использованием & и &&.

Я знаю, что второе - это ссылка на r-value, и что она позволяет обратиться к ресурсам
объекта. Но ведь в обоих случая создается новый объект. В чем тогда профит?
    


Ответы

Ответ 1



Конструктор перемещения это чисто конвенциональная конструкция(так же как и конструктор копирования, собственно), поэтому никто не мешает делать в любом конструкторе всё, что заблагорассудится. Другое дело, что программист, использующий Ваш класс, будет ожидать перемещения от конструктора перемещения и никак не будет ожидать такого подвоха от конструктора копирования. В том числе и поэтому, конструктор копирования всегда объявляется с константным аргументом, а не как у Вас. Ещё раз, конструкторы имеют свои названия исходя из того, что они делают по умолчанию(т.е. тогда, когда их генерирует компилятор). Соответственно, программист тоже должен делать так, как делается по умолчанию, а не придумывать реализацию, которая больше похожа на саботаж, чем на что-либо другое. Исходя из всего вышесказанного: Ваш вопрос не имеет смысла. Отличным примером перемещающего конструктора копирования является std::auto_ptr. Все мы знаем его судьбу.

Ответ 2



Существует несколько категорий выражений, наболее известные - это "rvalue" и "lvalue". Переменная с типом T&& может принимать только r-value выражение, переменная с типом T& может принимать только не константные l-value выражения. Совершенно не важно, что это за переменная - агрумент конструктора или нет. Соответственно разница между SomeClass( SomeClass &&other ) и SomeClass( SomeClass &other ) - только в том, что первый будет вызываться только для r-value выражений, а второй - только для неконстантных l-value выражений. При этом ни один из них не может принимать const SomeClass &. Что именно делают конструкторы - никому не важно, то что у них одинаковый код - это тоже не важно. Единственное отличие - это категории выражений, которые они могут принимать.

Ответ 3



Ваш конструктор копирования изменяет состояние объекта, с которого делается копия. Соответственно, такую операцию уже нельзя в полной мере назвать копированием. Правильная сигнатура конструктора копирования такая: SomeClass( const SomeClass &other )

Ответ 4



В чем тогда профит? Rvalue ссылки позволяют программистам избегать логически ненужного копирования и обеспечивать возможность идеальной передачи (perfect forwarding). Прежде всего они предназначены для использования в высоко производительных проектах и библиотеках.

Замена конструкторов статическим методом генерации

#java #конструктор


Прочел что можно заменить конструктор статическим методом генерации.

У меня в классе есть два приватных поля

private String one,
и 
private String two,


и нужно создать два конструктора разных конструктора для этих разных переменных.
компилятор ругается на одинаковую сигнатуру.

можете подсказать, как это можно решить с помощью статического метода генерации.
    


Ответы

Ответ 1



public class User { private String username; private String email; private User(String username, String email) { this.username = username; this.email = email; } public static User buildWithEmail(String email){ return new User(null, email); } public static User buildWithUsername(String username){ return new User(username, null); } }

Ответ 2



Возможно стоит использовать шаблон проектирования builder. В отдельном классе задаем поля one, two с помощью сеттеров и затем методом build будет создаваться требуемый экземпляр класса Например: public class MyClassNameBuilder { private String one; private String two; public MyClassNameBuilder withOne(String one) { this.one = one; return this; } public MyClassNameBuilder withTwo(String two) { this.two = two; return this; } public MyClassName build() { return new MyClassName() ... используем one, two в конструкторе или с помощью сетеров } }

Ответ 3



Могу предложить 4 варианта: Изменение статических полей класса в статическом блоке инициализации. Конструктор (String, String) Конструктор (String, Enum) Инициализация двойными скобками Стоит отметить, что Double Brace можно считать хорошей альтернативой шаблону проектирования Builder, но увлекаться в его применении не стоит (Подробнее...) ~ 5. Можете капнуть в сторону вложенных классов. Java Code (Main.java) public class Main { public static void main(String[] arg){ Example example1 = new Example("one", Example.Method.ONE); Example example2 = new Example("one", "two"); Example example3 = new Example(){{ setOne("one"); setTwo("two"); }}; } } Java Code (Example.java) public class Example { private static String one; private static String two; static { one = "one"; two = "two"; } public enum Method{ ONE, TWO } Example(){ // PASS } Example(String text, Method method){ if (method == Method.ONE){ Example.two = text; } else if (method == Method.TWO){ Example.one = text; } else { throw new IllegalArgumentException("..."); } } Example(String one, String two){ Example.one = one; Example.two = two; } public static void setOne(String one) { Example.one = one; } public static String getOne() { return one; } public static void setTwo(String two) { Example.two = two; } public static String getTwo() { return two; } }

Java. Обязательно ли использовать this. в коде конструктора, присваивающего значения переменным класса?

#java #конструктор


В конструкторе, предназначенном для присвоения значений переменным класса при создании
объекта, иногда использую this., иногда не используют. Я проверил - оба варианта работают,
является ли один из них предпочтительным?

public class Vehicle {
  private String color;
  Vehicle(String c) {
    color = c;
  }
}




public class Vehicle {
  private String color;
  Vehicle(String c) {
    this.color = c;
  }
}

    


Ответы

Ответ 1



Необязательно, но позволяет не изобретать имена аргументам конструктора: public class Vehicle { private String color; Vehicle(String color) { this.color = color; } }

Ответ 2



Предпочтительно и, в принципе, по другому и не получится в случае, если имя аргумента совпадает с именем поля класса: public class Vehicle { private Color color; Vehicle(Color color) { this.color = color; } } В случаях, когда имя параметра отличается от имени поля, которому будет присваиваться значение - слово this необязательно. Оно и так будет подразумеваться.

Странный конструктор

#c_sharp #конструктор


User user1 = new User { Name = "Tom", Age = 33 };


Почему в конструкторе используются фигурные скобки?

Интуиция мне говорит что это связано со свойствами. 
    


Ответы

Ответ 1



Формально выражение создания объекта определяется следующей грамматикой выражение-создания-объекта: new тип ( список аргументов ) инициализатор-объекта new тип инициализатор объекта где инициализатор объекта в свою очередь определяется как Инициализатор-объекта: { список-инициализаторов-элементов } То есть в этом предложении User user1 = new User { Name = "Tom", Age = 33 }; выполняются две вещи. Первое - это вызывается конструктор по умолчанию, а затем свойствам Name и Age в инициализаторе объекта присваиваются значения. Эта строка аналогична следующему коду: User user1 = new User(); user1.Name = "Tom"; user1.Age = 33; При этом свойства Name И Age должны быть доступны, например, объявлены с модификатором public. Вы можете также использовать в этой конструкции конструктор с параметрами и вполне возможно, что конструктор также инициализирует эти свойства, но их значения в конечном итоге будут переписаны значениями в из инициализатора объекта. Такая форма удобна при работе со структурами, когда имеется только конструктор по умолчанию, и вы можете в одном предложении вызвать этот конструктор и инициализировать поля структуры, а не писать несколько предложений с отдельными присваиваниями.

Ответ 2



В данном случае Вы инициализируете свойства Вашего объекта. В этом нет ничего странного. Если бы Вы написали параметризированный конструктор User(string name, int age) { Name = name; Age = age; } И вызвали его при создании экземпляра класса User user1 = new User("Tom", 33); То результат был бы идентичным, инициализация свойств Name и Age. Но в них есть некоторые отличия. При использовании инициализатора { } и конструктора по умолчанию (Можно вызывать любой конструктор, но в большинстве случаев в этом нет необходимости) - в свойствах будут записаны те значения, которые объявлялись в инициализаторе. User user1 = new User(){ Name = "Vasilii", Age = 45 }; Так происходит потому что значения описанные в инициализаторе присваиваются после вызова конструктора. Или же это делается для простоты понимания Вашего кода. Ведь согласитесь, что такой код User user1 = new User { Name = "Tom", Age = 33 }; выглядит лучше и компактнее, чем User user1 = new User(); user1.Name = "Tom"; user1.Age = 33;

Как избежать определения двух методов/конструкторов с одинаковыми параметрами?

#java #методы #конструктор #полиморфизм


Например, пишу класс Vector. Вполне естественно создавать объект, принимая в конструктор
или координаты x и y, или же принимая длину вектора и угол между направлением вектора
и положительным направлением оси OX. Однако я не могу написать так:

public class Vector {
    float x;
    float y;

    public Vector(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public Vector(float length, float alpha) {
        this.x = length*Math.cos(alpha);
        this.y = length*Math.sin(alpha);
    }
}


Компилятор, конечно, будет ругаться на неоднозначность в определении конструкторов.
Я не могу её избежать переставив аргументы местами, так как они одинаковых типов. Что
делать в таких ситуациях, когда метод/конструктор должен принимать два разных по смыслу
набора параметров, но получается так, что они имеют одинаковые типы?
    


Ответы

Ответ 1



В таких случаях не лишним будет использование статических фабричных методов. С использованием этого шаблона ваш код будет выглядить так: public static class Vector { public final float x; public final float y; private Vector(float x, float y) { this.x = x; this.y = y; } public static Vector coordinate(float x, float y) { return new Vector(x, y); } public static Vector radian(float length, float alpha) { return new Vector(length * Math.cos(alpha), length * Math.sin(alpha);) } } Так же, можно воспользоваться шаблоном проектирования builder. Здесь он выглядит немного громоздко, но в принципе, тоже применим: public static class Vector { ... public static VectorBuilder builder() { return new VectorBuilder(); } public static class VectorBuilder { private double length; private double alpha; private float x; private float y; public VectorBuilder setLength(float length) { this.length = length; compute(); return this; } public VectorBuilder setAlpha(float alpha) { this.alpha = alpha; compute(); return this; } private void compute() { this.x = (float) (length * Math.cos(alpha)); this.y = (float) (length * Math.sin(alpha)); } public VectorBuilder setX(float x) { this.x = x; return this; } public VectorBuilder setY(float y) { this.y = y; return this; } public Vector build() { return new Vector(x, y); } } }

Ответ 2



Что-то вроде фабрики: interface SomeInterface { float setX(float x, float y); float setY(float x, float y); } class Vector { float x; float y; static SomeInterface case1 = new SomeInterface() { @Override public float setX(float x, float y) { return x; } @Override public float setY(float x, float y) { return y; } }; static SomeInterface case2 = new SomeInterface() { @Override public float setX(float x, float y) { return x * (float) Math.cos(y); } @Override public float setY(float x, float y) { return x * (float) Math.sin(y); } }; public Vector(float x, float y, SomeInterface someInterface) { this.x = someInterface.setX(x,y); this.y = someInterface.setY(x,y); } } . . . new Vector(1.0f, 2.0f, Vector.case1); new Vector(1.0f, 2.0f, Vector.case2);

Ответ 3



Можете добавить костыль в виде третьего параметра и оставить один конструктор: public Vector(float a, float b, boolean isCoordinates) { if (isCoordinates) { this.x = a; this.y = b; } else { this.x = a*Math.cos(b); this.y = a*Math.sin(b); } }

Ответ 4



Может так? public class Vector { float x; float y; public Vector(float[] ху) { this.x = ху[0]; this.y = ху[1]; } public Vector(float length, float alpha) { this.x = length*Math.cos(alpha); this.y = length*Math.sin(alpha); } }

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

“explicit” для конструкторов копирования и перемещения

#cpp #cpp11 #конструктор #конструктор_копирования


Надо ли (и если да, то в каких случаях) указывать конструкторы копирования и перемещения
как explicit? Видел такое в коде и не совсем понял. Где ненужное неявное преобразование,
которое explicit помогает нам избежать?
    


Ответы

Ответ 1



Ответ на этот вопрос прост: explicit нужен там, где он нужен, а где не нужен — там не нужен. Есть распространённый совет, что все конструкторы, что принимают один аргумент должны быть помечены explicit, во избежание случайного создания объектов класса там, где их создание не очевидно. К примеру, есть у нас функция void someFun(const std::shared_ptr&) и мы, совершенно случайно, используем её так: int* array = new int[55]; someFun(array); delete[] array; Если бы конструктор std::shared_ptr не был explicit, то код бы успешно скомпилировался и на третьей строчке мы бы получили неопределённое поведение. Но т.к. конструктор std::shared_ptr помечен как explicit мы не получим такую ситуацию случайно. Таким образом, explicit призван исключить случайные ошибки. Что касается explicit конструктора копирования: ни разу не видел такого и не могу придумать, зачем такой может быть нужен. Если мы хотим запретить копирование, то нужно удалить его вообще, если не хотим, то зачем вставлять палки в колёса разрешая лишь узкий набор синтаксических вариантов, используя которые можно будет скопировать объект? В качестве примера можно привести следующий код: #include #include using namespace std; class MeClass { public: MeClass(size_t idx, const string& tag = "") : m_Idx{idx}, m_Tag{tag} { } MeClass(const MeClass& rhs): m_Idx{rhs.m_Idx}, m_Tag{rhs.m_Tag} { } private: size_t m_Idx; string m_Tag; }; MeClass explicitReturn() { // Явный вызов конструктора // Неявный вызов конструктора копирования // move нужен исключительно для исключения C++17 RVO return move(MeClass{2, "meTag"}); } MeClass implicitReturn() { // Неявный вызов конструктора // Неявный вызов конструктора копирования (до C++17) return {3, "meaTag"}; } void funForFun(const MeClass& me) { } int main() { // Явный вызов конструктора MeClass me1{1}; // Неявный вызов конструктора MeClass me2 = 2; // Неявный вызов конструктора копирования auto me3 = me1; // Явный вызов конструктора копирования auto me4{me1}; // Неявный вызов конструктора funForFun(2); funForFun({3}); // Явный вызов конструктора funForFun(me1); }; Если пометить в коде конструктор как explicit, то перестанут работать примеры, где указано явное его использование. То же самое с конструктором копирования.

Ответ 2



Ключевое слово explict позволит избежать случайного преобразования типов, которое может привести к неявным ошибкам. Например: У вас есть класс с конструктором MyString(int size), который создает строку размером size. Также есть метод print(const MyString&), при вызове print(3) ты можешь ожидать вызов print("3"), а на самом деле получишь пустую строку из 3 символов. Хороший и развернутый ответ можно найти тут.

Ответ 3



Если у Вас будут в дальнейшем определены или явно удалены операторы копирования и перемещения, то в этом нет никакой обходимости.

пятница, 10 января 2020 г.

Конструктор по умолчанию

#cpp #классы #перегрузка_операторов #конструктор #language_lawyer


class A
{
  public:
   A():a(0),b(0) {}
   explicit A(int x): a(x), b(0) {}
   A(int x, int y): a(x), b(y) {}
  private:
   int a,b;
};


и

class A
{
  public:
   explicit A(int x=0, int y=0): a(x), b(y) {}
  private:
   int a,b;
};


Есть ли различия? Что лучше использовать?
    


Ответы

Ответ 1



Эти два объявления классов не эквивалентны. Во втором объявлении класса конструктор объявлен со спецификатором функции explicit, а это ограничивает применение этого конструктора в различных ситуациях. В первом же объявлении класса только конструктор преобразования объявлен со спецификатором функции explicit. А это означает, что другие конструкторы вы можете вызывать неявно. То есть первое объявление предоставляет больше возможностей по использованию класса. Рассмотрите следующую демонстрационную программу #include struct A { explicit A( int x = 0, int y = 0 ) : x( x ), y( y ) {} int x; int y; }; struct B { B() : x( 0 ), y( 0 ) {} explicit B( int x ): x( x ), y( 0 ) {} B( int x, int y ): x( x ), y( y ) {} int x; int y; }; void f( const A &a ) { std::cout << "a.x = " << a.x << ", a.y = " << a.y << std::endl; } void g( const B &b ) { std::cout << "b.x = " << b.x << ", b.y = " << b.y << std::endl; } int main() { // f( {} ); // f( { 1, 2 } ); g( {} ); g( { 1, 2 } ); } Ее вывод на консоль: b.x = 0, b.y = 0 b.x = 1, b.y = 2 В этой программе два вызова функции f закомментированы, так как если их раскомментировать, то компилятор выдаст сообщение об ошибке. Другое важное отличии состоит в том, что один класс имеет всего лишь один конструктор с заданной сигнатурой, а другой класс имеет три конструктора с различными сигнатурами. Рассмотрите еще один демонстрационный пример struct A { explicit A( int x = 0, int y = 0 ) : x( x ), y( y ) {} int x; int y; }; struct B { B() : x( 0 ), y( 0 ) {} explicit B( int x ): x( x ), y( 0 ) {} B( int x, int y ): x( x ), y( y ) {} int x; int y; }; struct C { //friend A::A(); friend B::B(); }; int main() { } Здесь в классе C вы можете объявить конструктор по умолчанию класса B в качестве друга класса С. Однако вы не можете сделать то же самое с конструктором по умолчанию класса A, чтобы объявить его другом класса C, так как конструктор по умолчанию в классе A имеет другую сигнатуру. Вам уже придется писать struct C { friend A::A( int, int ); }; а это может быть не тем, что вы хотели бы получить. То есть если вы, например, хотели, чтобы другом был конструктор, который вызывается исключительно без аргументов. То есть, опять-таки, когда имеются отдельные конструкторы, то ваши возможности более широкие. Если рассматривать не конструкторы, а функции, то разница имеется еще более существенная. Аргументы по умолчанию не влияют на тип функции. Поэтому, например, если вы объявили функцию как void f( int, int = 0 ); то, несмотря на аргумент по умолчанию и того факта, что вы можете ее вызывать как f( value ); тем не менее ее тип void( int, int ). А это в свою очередь означает, что вы не можете, например, написать void h( void f( int ) ) { f( 10 ); } void f( int x, int y = 0 ) { std::cout << "x = " << x << ", y = " << y << std::endl; } // h( f ); так как параметр функции h имеет тип void( int )., а у функции, используемой в качестве аргумента, тип void( int, int ) Если же вы объявите две функции вместо одной void h( void f( int ) ) { f( 10 ); } void f( int x ) { std::cout << "x = " << x << std::endl; } void f( int x, int y ) { std::cout << "x = " << x << ", y = " << y << std::endl; } то данный вызов h( f ); будет корректным, так как имеется функция с одним параметром.

Ответ 2



Различия уже объяснил @Vlad from Moscow, я лишь предложу, из двух вариантов в вопросе, третий вариант: class A { public: A():A(0, 0) {} explicit A(int x): A(x, 0) {} A(int x, int y): a{x}, b{y} {} private: int a,b; }; На мой взгляд именно этот вариант является лучшим, т.к. он имеет явный конструктор с одним аргументом, что является хорошей практикой и уберегает от некоторых ошибок. С другой стороны, explicit для конструкторов, у которых больше или меньше одного аргумента, на мой взгляд, является лишним. Т.к. случайно создать объект из более чем одного аргумента проблематично, а именно за этим мы и приписали explicit у одноаргументнова конструктора — защита от случайных ошибок. Ну и самое главное, мы имеем лишь один конструктор, который инициализирует поля. Все остальные действуют через него, что позволяет минимизировать ошибки инициализации.

Порядок инициализации класса (конструкторов) родителя и наследника при нескольких конструкторах в обоих классах

#java #конструктор


Нашла следующий порядок инициализации объекта для случая наследования классов:


Статические поля класса Parent;
Статический блок инициализации класса Parent;
Статические поля класса Сhild;
Статический блок инициализации класса Child;
Нестатические поля  класса Parent;
Нестатический блок инициализации класса Parent;
Конструктор класса Parent;
Нестатические поля  класса Сhild;
Нестатический блок инициализации класса Сhild
Конструктор класса Сhild.


Но что делать, если несколько конструкторов и в родителе, и в наследнике - каким
образом они загружаются? Все конструкторы родителя, а потом наследника или по одному
в порядке написания?

На собеседовании мне показали пример возможной ошибки в конструкторе (или его исполнения
- точно не помню) наследника из-за порядка конструкторов родителя, но я не могу найти
нигде внятной информации и примеров такой ситуации в коде. Можете разъяснить этот момент?
    


Ответы

Ответ 1



В классе-наследнике вызывается один конструктор родителя. Либо конструктор родителя вызывается явно в начале конструктора наследника с помощью конструкции super, либо вызывается неявно при подстановке super(); в начало конструктора компилятором. При этом если у родителя нет конструктора без аргументов, то код приведёт к ошибке компиляции. Документация по этому поводу. Таким образом, нет неопределённости с тем, сколько конструкторов родителя и какие именно вызываются в потомке. Проблемы из-за порядка вызова конструкторов и инициализации полей классов могут возникнуть, как писал @zRrr, если в конструкторе родителя вызывается переопределяемый или реализуемый потомком метод, который в свою очередь использует ещё не инициализированные поля потомка. Пример: public abstract class Animal { private final String fullName; protected Animal() { fullName = "Animal " + getName(); } public String getFullName() { return fullName; } protected abstract String getName(); } public class Dog extends Animal { private String thisName = "DDog"; protected String getName() { return thisName; } } public static void main(String[] args) { Animal animal = new Dog(); System.out.println(animal.getFullName()); } На экран будет выведено Animal null, так как на момент вызова метода getName у Dog поле thisName ещё не инициализировано. При создании animal происходит вызов конструктора по умолчанию у Dog, который приводит сначала к вызову конструктора без параметров у Animal, а только затем к инициализации поля thisName. Ещё есть такой пример, взятый из этой статьи: public class Upper { String upperString; public Upper() { Initializer.initialize(this); } } public class Lower extends Upper { String lowerString = null; public Lower() { super(); System.out.println("Upper: " + upperString); System.out.println("Lower: " + lowerString); } public static void main(final String[] args) { new Lower(); } } public class Initializer { static void initialize(final Upper anUpper) { if (anUpper instanceof Lower) { Lower lower = (Lower)anUpper; lower.lowerString = "lowerInited"; } anUpper.upperString = "upperInited"; } } На экран будет выведено: Upper: upperInited Lower: null; Но если заменить String lowerString = null; на String lowerString;, то вывод будет: Upper: upperInited Lower: lowerInited Проблема в первоначальном варианте в том, что присваивание null в String lowerString = null; происходит после вызова конструктора родителя, в котором в свою очередь вызывается метод Initializer.initialize. То есть в lowerString сначала записывается "lowerInited", а затем null. Оба примера демонстрируют проблемы, возникающие из-за неправильных предположений о порядке выполнения кода. Сложность осознания того, что за чем выполняется в данном коде, сама по себе уже говорит о том, что на практике код так писать не стоит.

Ответ 2



Думаю такой порядок: /** * Порядок инициализации таков: * [1]. Статические поля базового класса; * [2]. Статический блок инициализации базового класса; * [3]. Статические поля производного класса; * [4]. Статический блок инициализации производного класса; * [5]. Поля (Глобальные переменные) базового класса; * [6]. Нестатический блок инициализации базового класса; * [7]. Конструктор базового класса [если у родителя нет конструктора без аргументов, то код приведёт к ошибке компиляции]; * [8]. Поля (Глобальные переменные) производного класса; * [9]. Нестатический блок инициализации производного класса; * [10]. Конструктор производного класса; */ class Base { //[1] transient переменные и статические поля не сериализуются private static Base instance = new Base("))(("); //BASE = 0 // public static int _baseStaticInsect = 111; //сокрыто, доступ Base._baseStaticBase private static final int DELTA = 2; private static int BASE = 3; //[2] static { System.out.println("[static instance class initializer] " + Inherit.class.getSimpleName()); } //[5] private int i = 5; protected int j; //mod:protected-internal int x; int internal; //mod:package public int iInc; //[6] { System.out.println("[instance class initializer] " + this.getClass().getSimpleName()); } //[7] //private недоступен из вне, нельзя создать без рефлексии //если у нет конструктора без аргументов или он приватный, то код приведёт к ошибке компиляции]; public Base() { System.out.println("[constructor] " + this.getClass().getSimpleName()); x = BASE + DELTA; } //[7] public Base(String data) { this(); System.out.println("[constructor] (String)" + this.getClass().getSimpleName()); System.out.println(data); } static int printInit(String s) { return -1; } } public class Inherit extends Base { //[3] public static int _baseStaticBase = 222; public static int _baseStaticInherit = 4; //[4] static { System.out.println("[static class initializer] " + Inherit.class.getSimpleName()); } //[8] private int internal; public int Integnal() { //getInternal return this.internal; } private boolean internalFlag; public boolean isInternalFlag() { return internalFlag; } //[9] { System.out.println("[instance class initializer] Inherit " + this.getClass().getSimpleName()); } //[10] public Inherit() { this("start"); System.out.println("Inherit constructor"); } //[10] public Inherit(String data) { System.out.println("Inherit constructor data"); } public static void main(String[] args) { Base base = new Base(); Inherit inherit = new Inherit(); } }

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

Приведение типа в списках инициализации, как лучше?

#cpp #конструктор


У меня в классе есть поле std::uint64_t m_value. В конструкторе по умолчанию я пишу
в списке инициализации : m_value(0). Работаю с разными компиляторами, при этом можно
в настройках проекта отрегулировать вывод предупреждений. Некоторые никак не реагируют
на такую инициализацию, некоторые пишут что-то про приведение типа.

Вопрос: надо ли продолжать писать так или же стоит явно прописывать приведение типа,
например, : m_value((std::uint64_t)(0)), или через static_cast? Как считается лучше
всего делать? Я понимаю, что неявное приведение типа, конечно же выполняется, но может
имеет смысл для читающего код человека показывать, что имелось ввиду?
    


Ответы

Ответ 1



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

Ответ 2



По-моему, дело вкуса... Если бы это был шаблон - я бы выполнял приведение к типу-параметру T, но в вашем случае ведь проще использовать литерал соответствующего типа - ну, там, 0llu...

суббота, 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 г.

Синтаксис инициализации const членов класса

#cpp #классы #конструктор


Есть класс

class object
{
  public: object();
  private: const double mass;
};


Инициализация const double mass в конструкторе отрабатывает только так:

object::object(): mass(1) { }


Почему не проходит такой вариант? 

object::object() { 
   this->mass = 1;
}


И как в таком случае инициализировать const члены класса, которые должны предварительно
высчитываться относительно входных параметров конструктора?

Какие варианты в C++ удобны для объявления/инициализации const членов класса?
    


Ответы

Ответ 1



Когда выполняется тело конструктора, то все члены данных объекта уже созданы. Поэтому в этом конструкторе object::object() { this->mass = 1; } в предложении this->mass = 1; применяется оператор присваивания, который нельзя использовать с константными объектами. Если вам нужно определить значение константного члена данных на основе некоторых вычислений, то в качестве инициализатора используйте некоторую, например, статическую функцию - член класса. object::object( some_argument ): mass( some_function( some_argument ) ) { } Аргументами функции не обязательно должны быть аргументы конструктора. Можно использовать статические члены данных класса, не статически члены данных, которые уже были инициализированы в списке инициализации (лги в объявлении класса должны предшествовать, или объекты, которые находятся в области видимости определения конструктора. Ниже показан пример #include class Object { public: Object( double x ) : x ( 2 * x ), mass( init( this->x ) ) { } double get_mass() const { return mass; } private: double x; const double mass; static double init( double x ) { return x < 0 ? -x / 2 : x; } }; int main() { Object obj( 10 ); std::cout << obj.get_mass() << std::endl; return 0; }