17.1.4.  Creation Rules and Friend Functions

(What Friends Are Really For)

[ fromfile: factory.xml id: creationalrules ]

You can design a class to have a creation rule, such as: All new Customers must be created indirectly through a CustomerFactory. You can enforce this rule by defining only non-public constructors. In particular, make sure that you make the copy constructor and the assignment operator non-public. If you omit them from the class definition, the compiler will supply public versions of these two member functions, thus producing two “loopholes” in your creation rule. For example, this might permit clients to create Customer instances that are not children of your factory (which could lead to memory leaks). It is important to document this rule clearly in your class definition as we did with the comment in Example 17.9.

Example 17.9. src/libs/customer/customer.h

[ . . . . ]

class CUSTOMER_EXPORT Customer : public DataObject {
    Q_OBJECT
[ . . . . ]

    friend class CustomerFactory;
  protected:
    Customer(QString name=QString()) { 1
        setObjectName(name);
    }

    Customer(QString name, QString id, CustomerType type);

1

We declared the constructors of Customer as protected, so all Customer objects must be created indirectly through a CustomerFactory.

In Example 17.9, the constructors are protected. [49] CustomerFactory is declared to be a friend class inside Customer. This gives CustomerFactory permission to access the non-public members of Customer. In this way, we have made it impossible for client code to create Customer objects except through the CustomerFactory. In Example 17.10, we have a similar setup with the various Address classes. The base class, Address, is abstract.

Example 17.10. src/libs/dataobjects/address.h

[ . . . . ]
class DOBJS_EXPORT Address : public ConstrainedDataObject {
    Q_OBJECT
  public:
  [ . . . . ]

  protected:
    Address(QString addressName = QString()) { 1
        setObjectName(addressName);
    }
  public:
    virtual Country::CountryType getCountry() = 0;
[ . . . . ]

private:
    QString m_Line1, m_Line2, m_City, m_Phone;
};

1

protected constructor

In the derived classes, defined in Example 17.11 we have protected the constructors and given friend status to a factory, this time DataObjects::ObjectFactory. This gives ObjectFactory permission to access the non-public members of UsAddress and CanadaAddress.

Example 17.11. src/libs/dataobjects/address.h

[ . . . . ]

class DOBJS_EXPORT UsAddress : public Address {
    Q_OBJECT
  public:
    Q_PROPERTY( QString State READ getState WRITE setState );
    Q_PROPERTY( QString Zip READ getZip WRITE setZip );
    friend class ObjectFactory; 1
  protected:
    UsAddress(QString name=QString()) : Address(name) {}
    static QString getPhoneFormat();
  public:
    static void initConstraints() ;
[ . . . . ]
      
  private:
    QString m_State, m_Zip;
};

class DOBJS_EXPORT CanadaAddress : public Address {
    Q_OBJECT
  public:
    Q_PROPERTY( QString Province READ getProvince 
                WRITE setProvince );
    Q_PROPERTY( QString PostalCode READ getPostalCode 
                WRITE setPostalCode );
    friend class ObjectFactory; 2
  protected:
    CanadaAddress(QString name=QString()): Address(name) {}
    static QString getPhoneFormat();
  public:
    static void initConstraints() ;
[ . . . . ]

  private:
    QString m_Province, m_PostalCode;
};

1

All new UsAddress objects must be created indirectly through an ObjectFactory.

2

All new CanadaAddress objects must be created indirectly through an ObjectFactory.

It is now impossible to create Address objects except through the factory or from the derived classes.

[Note]Note

If you are writing a multi-threaded application that uses an ObjectFactory, you need to be careful about the ownership of objects.

Making a QObject the parent of a child in another thread will result in an error message such as the following one:

New parent must be in the same thread as the previous parent", file 
kernel/qobject.cpp, line 1681
Aborted

To make our ObjectFactory work in a multi-threaded environment, we can:

  1. Explicitly pass the parent when we create each object (to prevent the default parenting behavior)

  2. Write the factory's singleton() function to return an object that belongs to the local thread.



[49] This permits us to write derived classes that reuse this constructor.