2

I've designed a class InputField having a constructor with 2 parameters : explicit InputField(InputFieldIndex uiIndex, QString sName) noexcept;

I store this class in a QVector, so, unfortunately, I have to add a default-contructor. In term of software design, that's not good for me (because I have a constraint on uiIndex). Is there a way to only allow to QT (QVector in my case) to call the default constructor? A macro or a preprocessor instruction?

Drise
  • 4,310
  • 5
  • 41
  • 66
Joseph Garnier
  • 117
  • 1
  • 10
  • 3
    How about using `std::vector` instead? Otherwise, storying `std::optional` or some other proxy type might also be worth considering. – François Andrieux May 16 '18 at 15:27
  • @FrançoisAndrieux I can't, it's a full Qt application. – Joseph Garnier May 16 '18 at 15:28
  • Reminds me of a very old joke: 'Doctor, it pains when I do this (patient performs a very bizarre gesture with his hand and ear). What can you suggest? And the good doctor: I suggest not doing this.' – SergeyA May 16 '18 at 15:28
  • 2
    What 'full Qt' means? Somebody is forcing you to use QVector? – SergeyA May 16 '18 at 15:29
  • 2
    @Milleras You cannot use the C++ standard library? – JBL May 16 '18 at 15:29
  • 1
    @Milleras Are you forbidden from using standard types? Otherwise it seems like `QVector>` should do the job. – François Andrieux May 16 '18 at 15:32
  • @FrançoisAndrieux That's pointless. `std::vector` will work just fine for a non-default-constructible type. – Kuba hasn't forgotten Monica May 16 '18 at 20:28
  • @FrançoisAndrieux std::optional only work for c++ 17. Unfortunately for me, I have to use c++ 14. – Joseph Garnier May 17 '18 at 13:29
  • @JBL In a Qt application, it's recommanded to use only Qt containers. In my case, my QVector is used in a QAbstractTableModel and in a custom view. If I use any STL container, I will have to copy their content in a Qt container to be compatible with Qt. See https://stackoverflow.com/questions/1668259/stl-or-qt-containers – Joseph Garnier May 17 '18 at 13:55

2 Answers2

5

You can try to make QVector<InputField> a friend class.

Consider the following toy example:

class Foo {
    // Grant QVector<Foo> access to this class' internals
    friend class QVector<Foo>;
public:
    explicit Foo(int a, bool b) {
        // Empty
    }
private:
    Foo() {
        // Callable by this class and QVector<Foo>.
    }
};

With this, it is possible to store instances of Foo in a QVector<Foo>:

QVector<Foo> myList;
myList << Foo(1, true) << Foo(2, false);

However, the following would e.g. fail:

Foo foo;
Martin Höher
  • 715
  • 5
  • 9
  • 1
    A constructor with no argument doesn't need to be explicit. – SergeyA May 16 '18 at 15:32
  • Good idea! It works, thank you. I've added `InputField() = default;` and `friend class QVector;` – Joseph Garnier May 16 '18 at 15:34
  • @SergeyA: Thanks for the hint ;-) I'll update the answer. – Martin Höher May 16 '18 at 15:40
  • 3
    @Milleras Beware that this means it's possible to now access default constructed instances using `QVector`, meaning you could still use an invalid instance. And as it stands it's not necessarily easy to detect this case. – François Andrieux May 16 '18 at 15:40
  • This solution closely binds `Foo` to `QVector`, never mind that the default constructor exists -- perhaps `Foo` uses non-default-constructible members? The goal is to make it so that `QVector` can use a non-default-constructible `Foo`, without changing anything about `Foo`. This points clearly to what the solution should be: specializations of `QVector` members for the non-copyable type `Foo`. – Kuba hasn't forgotten Monica May 16 '18 at 20:30
  • @FrançoisAndrieux Yes, that's a good point. But for now, I don't see other solution for my problem :( – Joseph Garnier May 17 '18 at 13:34
1

There's no need for QVector to call the default constructor; std::vector doesn't, after all - not unless you use one of its methods that need that constructor. All you need is to disable default-construction in QVector:

#define QVECTOR_NON_DEFAULT_CONSTRUCTIBLE(Type) \
template <> QVector<Type>::QVector(int) = delete; \
template <> void QVector<Type>::resize(int newSize) { \
   Q_ASSERT(newSize <= size()); \
   detach(); \
} \
template <> void QVector<Type>::defaultConstruct(Type*, Type*) { Q_ASSERT(false); }

Then, put the macro in the same place you'd put Q_DECLARE_METATYPE: right where the type is declared:

class InputField {
  ...
};
QVECTOR_NON_DEFAULT_CONSTRUCTIBLE(InputField)

Then you can use QVector<InputField> with same limitations as std::vector would have.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Do you suggest to extend QVector ? Else, how can I use your macro ? – Joseph Garnier May 17 '18 at 13:40
  • @JosephGarnier The macro provides specializations of `QVector`'s methods. That's the beauty of templated types: you can easily replace their bits and pieces for special circumstances! – Kuba hasn't forgotten Monica May 17 '18 at 18:56
  • I get error : "function QVector::QVector(int size) cannot be specialized in the current scope" (for me it's normal) – Joseph Garnier May 18 '18 at 08:11
  • 1
    You did something that I didn't do above, because the above works just fine. Recall that such specializations need to happen in namespace scope, not class scope. You need to specialize in Qt's namespace if any, in fact. Otherwise in global scope. – Kuba hasn't forgotten Monica May 18 '18 at 13:49
  • Thank for your help, I didn't know this trick. I tried but I have this error : error LNK2005: "private: void __cdecl QVector::defaultConstruct(class ExpTool::InputField *,class ExpTool::InputField *)" (?defaultConstruct@?$QVector@VInputField) already defined in pch.obj fatal error LNK1169: one or more multiply defined symbols found Content of my pch : #include "QtCore" #include "QtGlobal" #include "QDebug" #include "QGlobalStatic" – Joseph Garnier May 18 '18 at 17:40
  • Rebuild your project. As in: delete the build folder and build again. The way you troubleshoot linker errors *always* begins with a clean-slate rebuild. – Kuba hasn't forgotten Monica May 18 '18 at 18:05
  • 1
    Clean my project changed nothing but I found the mistake. You have to add `inline` before returned type in your functions. Otherwise, it works great ! Thank. – Joseph Garnier May 21 '18 at 14:59