Это ошибка в C++ Builder или в MinGw и MSVC?

Приведенный ниже код основан на Основные рекомендации CPP, Приложение C, «Обсуждение: используйте фабричную функцию, если вам нужно «виртуальное поведение» во время инициализации».

Идея кода заключается в использовании фабричной функции для создания экземпляров производных классов, которые требуют вызова «пост-конструктора» в производном классе.

Код у меня такой:

#include <iostream>
#include <memory>

using namespace std;

class B {
protected:
    class Token {};

public:
    // constructor needs to be public so that make_shared can access it.
    // protected access level is gained by requiring a Token.
    explicit B(Token) { /* ... */ }  // create an imperfectly initialized object

    template<class T>
    static shared_ptr<T> create()    // interface for creating shared objects
    {
        auto p = make_shared<T>(typename T::Token{});
        p->post_initialize();
        return p;
    }

private: //change to protected and it compiles in C++ Builder
    void post_initialize()   // called right after construction
    { /* ... */ f(); /* ... */ } // GOOD: virtual dispatch is safe
    virtual void f() = 0;
};


class D : public B {                 // some derived class
protected:
    class Token {};

public:
    // constructor needs to be public so that make_shared can access it.
    // protected access level is gained by requiring a Token.
    explicit D(Token) : B{ B::Token{} } {}
    D() = delete;

protected:

    //Make B:create() a friend so it can access private and protected members...

    template<class T>
    friend shared_ptr<T> B::create();

private: 

    void f() override {
        std::cout << "I'm a D" << std::endl;
    };
};


int main() {

    shared_ptr<B> p = B::create<D>();    // creating a D object

    return 0;
}

Этот код компилируется и запускается с использованием MinGW версии w64 9.0 и MSVC 2022. Однако C++Builder 11.5 Alexandria, компилятор Clang, 32-разрядный и 64-разрядный, жалуется:

ошибка: 'post_initialize' является закрытым членом 'B'

Но если я изменю раздел B класса private: на protected:, C++Builder скомпилирует код.

Я ожидал, что C++Builder скомпилирует это, так как B::create() должен иметь возможность вызывать private функцию B, не так ли?

Или я ошибаюсь, и MinGW и MSVC неправильно умеют это компилировать?

Похоже на ошибку в C++ Builder.

Jason Liam 06.01.2023 08:39

Если вы удалите объявление Token из класса D, вам даже не нужно объявлять B::create другом, потому что ему не нужно будет обращаться ни к одному элементу D. И D конструктор по-прежнему защищен B::Token.

sklott 06.01.2023 09:06

Токен необходим, чтобы предотвратить прямое создание экземпляра класса, но при этом оставить конструктор общедоступным, чтобы make_shared мог получить к нему доступ. Мне пришлось несколько раз перечитать код (который взят из основных правил CPP), прежде чем я понял, почему это так. Идея состоит в том, что базовый класс после создания экземпляра всегда вызывает конструктор сообщений в производных классах, не полагаясь на то, что пользователь сделает это, что было бы в случае, если бы пользователь мог вызвать конструктор напрямую...

AndyB 06.01.2023 09:35

Но пользователь не сможет вызвать конструктор напрямую, даже если вы удалите Token из D, потому что ему понадобится Token из B, что также является protected.

sklott 06.01.2023 12:49

@sklott Да, ты прав. Если я изменю конструктор D на явный D(B::Token) : B{ B::Token{} } {}, тогда я могу удалить объявление друга. И C++ Builder также может его скомпилировать. Похоже, ошибка в том, что C++ Builder неправильно понял выражение друга. Если я оставлю оператор друга в коде, даже с этим улучшением C++ Builder все равно не сможет его скомпилировать...

AndyB 06.01.2023 15:35

Какую версию Clang использует C++ Builder? Clang 14.0.0 принимает этот код.

Jerry Coffin 06.01.2023 16:50

@JerryCoffin Используется версия 5.0.2. Они хотят перейти на v14 или v15 для следующего крупного выпуска. Это далеко от последнего Clang — я не знаю, почему это так, но, вероятно, это связано с их расширениями компилятора, необходимыми для поддержки библиотек Delphi.

AndyB 06.01.2023 20:10

@AndyB: Да, обратная совместимость может объяснить большую мотивацию.

Jerry Coffin 06.01.2023 20:21
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
8
136
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я ожидал, что C++ Builder скомпилирует это, поскольку B::create() должен иметь возможность вызывать частную функцию B, не так ли?

Да, post_initialize разрешено использовать/вызывать как p->post_initialize() изнутри B::create(), поскольку оба принадлежат к одному классу.

Судя по вашему описанию ошибки, это похоже на ошибку C++ Builder.

Джейсон, спасибо, что подтвердили, что я не схожу с ума :-) Я обнаружил ошибку в Embarcadero...

AndyB 06.01.2023 09:35

Embarcadero сообщает, что это проблема Clang — до версии 7.1.0 код не компилируется. Начиная с версии 8.0 он компилируется. В настоящее время они находятся в процессе обновления компилятора C++ Builder C++ до CLang V14 или V15 для будущего выпуска...

AndyB 06.01.2023 11:12

@AndyB Понятно. Вы можете поделиться здесь ссылкой на эту ошибку, чтобы будущие читатели также могли найти ее здесь.

Jason Liam 06.01.2023 11:13

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

AndyB 06.01.2023 11:16

Они добавили эту проблему в свою систему отслеживания ошибок Jira как проблему RS-115956. Это проблема Clang, которая уже исправлена ​​в более поздних выпусках Clang и будет решена не позднее, чем в следующем крупном выпуске C++ Builder. Но, как показал @sklott в комментариях выше, этот код можно изменить, чтобы устранить необходимость в объявлении друга, и тогда проблема исчезнет.

AndyB 06.01.2023 20:14

Другие вопросы по теме