«Найден один или несколько многократно определенных символов». Статическая и дружественная функция

я пытаюсь использовать interface классы, и у меня есть следующее class структура:

IBase.h:

      #pragma once
class IBase
{
protected:
    virtual ~IBase() = default;

public:
    virtual void Delete() = 0;

    IBase& operator=(const IBase&) = delete;
};

IQuackable.h:

      #ifndef IQUACKABLE
#define IQUACKABLE

#include "IBase.h"
#include <iostream>


class IQuackable : public IBase
{
protected:
    IQuackable() = default;
    ~IQuackable() = default;

public:
    virtual void Quack() = 0;

    static IQuackable* CreateInstance();
};

#endif // 

Кряква.h:

      #pragma once
#include "IQuackable.h"

class MallardDuck : public IQuackable
{
private:
    MallardDuck();

protected:
    ~MallardDuck();

public:
    void Delete() override;
    void Quack() override;

    friend IQuackable* IQuackable::CreateInstance();
};

Кряква.cpp:

      #include "MallardDuck.h"

MallardDuck::MallardDuck() {}

MallardDuck::~MallardDuck() {}

void MallardDuck::Delete() { delete this; }

void MallardDuck::Quack()
{
    std::cout << "Quack!\n";
}

IQuackable* IQuackable::CreateInstance()
{
    return static_cast<IQuackable*>(new MallardDuck());
}

Также я создал класс RedHeadDuck.h и .cpp с тем же объявлением и определением, что и MallardDuck.

И, наконец, код основного класса:

      #include "MallardDuck.h"
#include "RedHeadDuck.h"

int main()
{
    IQuackable* mallardDuck = MallardDuck::CreateInstance();
    IQuackable* redHeadDuck = RedHeadDuck::CreateInstance();

    mallardDuck->Quack();
    redHeadDuck->Quack();
}

И тут у меня две ошибки:


LNK2005 "public: статический класс IQuackable * __cdecl IQuackable::CreateInstance(void)" (?CreateInstance@IQuackable@@SAPAV1@XZ), уже определенный в MallardDuck.obj".

LNK1169 "найден один или несколько многократно определенных символов".


Насколько я понял, проблема в двойном определении, но как это исправить?

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

Что я могу сделать?

1 ответ

Цели

Я полагаю, это то, что вы пытаетесь получить, приняв все сложные шаблоны:

  1. интерфейс, т. е. «несколько типов, один и тот же набор методов»
  2. какой-то абстрактный фабричный шаблон , то есть вам нужен «инстанциатор», который предоставляет статический метод (который не сильно отличается от глобальной функции) для вызова и возвращает экземпляры производных классов.
  3. запретить пользователям напрямую вызывать ctors производных классов
  4. позаботьтесь о dtors, внедрив Delete()метод

Требование 1-3

Существует по крайней мере 3 способа выполнить требование 1-3, как описано ниже:

1. Производные классы, скрывающие статический метод своей базы

Это самый простой способ, и он вполне способен к текущему main.cpp. Производные классы могут переопределять статические методы своего базового класса.

В файле и :

          // Replace this:
    // friend IQuackable* IQuackable::CreateInstance();

    // With this:
    static IQuackable* CreateInstance();

В файле MallardDuck.cpp(а также RedHeadDuck.cppпо аналогии):

      // Replace this:
// IQuackable* IQuackable::CreateInstance() {
//     return static_cast<IQuackable*>(new MallardDuck());
// }

// With this:
IQuackable* MallardDuck::CreateInstance() {
    return new MallardDuck();
}

Проблема в том, что: другие производные классы, которые не переопределяют и не скрывают, по-прежнему будут отображаться как «запасной вариант». Таким образом:

  1. если вы на самом деле не реализуете IQuackable::CreateInstance()(пока вам это не нужно), то после вызова через производный класс код не будет компилироваться и не будет давать понятную другим причину; или же
  2. если вы решите реализовать его, вам лучше создать в нем исключение, которое может удивить вашего пользователя; в противном случае вам придется вернуть nullptrили что-то в этом роде, что является наихудшей практикой в ​​C++ (это то, что мы делаем в C, поскольку он не поддерживает обработку ошибок на уровне языка; любая функция C++, которая не может выполнить свою работу, никогда не должна возвращаться).

В любом случае это не элегантно.

2. Примите абстрактный заводской шаблон

Для этого шаблона требуется взаимодействующий «фабричный класс», который является абстрактным; затем всякий раз, когда вы выводите конкретное шарлатанство, выводите также и его фабрику.

В вашем случае вам нужно будет набросать IQuackableFactory, разоблачение IQuackableFactory::CreateInstance(), затем выведите MallardDuckFactoryи RedHeadDuckFactory.

Уже есть много хороших примеров, поэтому я не буду демонстрировать их здесь.

3. Внедрение функций с помощью CRTP

Есть еще один способ делать вещи. На самом деле вы предоставляете «Дайте мне экземпляр этого класса!» особенность. Обычно мы используем CRTP (любопытно повторяющийся шаблон шаблона ), чтобы «внедрить» определенную функцию в класс.

Сначала напишите этот файл EnableCreateInstance.hpp:

      #ifndef _ENABLECREATEINSTANCE_HPP_
#define _ENABLECREATEINSTANCE_HPP_

template <class T>
struct EnableCreateInstance {
    static T* CreateInstance() { return new T(); }
};

#endif //_ENABLECREATEINSTANCE_HPP_

Затем в MallardDuck.h:

      // Add:
#include "EnableCreateInstance.hpp"

class MallardDuck : public IQuackable, public EnableCreateInstance<MallardDuck> {
private:
    MallardDuck();

    friend class EnableCreateInstance<MallardDuck>;

    ...

В файле RedHeadDuck.hсделать то же самое: включить заголовок, публично наследовать и объявить EnableCreateInstance<RedHeadDuck>как класс друзей.

Это обеспечивает большую гибкость: вы по-прежнему предоставляете интерфейс, но менее «агрессивно»: производные классы имеют свободу выбора, предоставлять или не предоставлять CreateInstance(). Если они это сделают, просто наследуйте и (если ctor сделан частным) объявите о дружбе; если нет, опустите дополнительное наследование.

Требование 4

Ну, на самом деле вы можете использовать delete thisв нестатическом методе non-dtor . Но:

  1. Вы должны убедиться (что может быть сложно), что доступ к элементам данных или виртуальным функциям удаленного объекта больше не осуществляется, иначе это приведет к неопределенному поведению;
  2. Вы оставляете своих пользователей с оборванными указателями на удаленный объект.

Таким образом, мы редко предоставляем такие "удалители" в современном C++. Вы можете получить все преимущества, которые он может предоставить, благодаря интеллектуальным указателям , а также возможность избежать UB и многое другое.

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