Policies/Library Code Policy: Difference between revisions
(fix the link) |
Gjditchfield (talk | contribs) |
||
(55 intermediate revisions by 22 users not shown) | |||
Line 1: | Line 1: | ||
This document describes some of the recommended conventions that should be applied in the KDE libraries (not applications). Respecting these guidelines helps create a | This document describes some of the recommended conventions that should be applied in the KDE libraries (not applications). Respecting these guidelines helps create a consistent API and also may help ease maintainence of the libraries later. While these conventions are not mandatory, they are important guidelines, and should be respected unless you have a good reason to disregard them. | ||
As an introduction, you should read the document [http:// | As an introduction, you should read the document [http://wiki.qt-project.org/index.php?title=API_Design_Principles Qt-Style C++ API Design Principles]. | ||
For KDE Frameworks, it is recommended to follow the [[Policies/Frameworks_Coding_Style | KDE Frameworks Coding Style]]. | |||
== Naming Conventions == | == Naming Conventions == | ||
In KDE, we basically follow the same naming conventions as Qt. | In KDE, we basically follow the same naming conventions as Qt. | ||
Class names | Class names start with a capital K. The rest is in camel case. Function names start with a lower case, but the first letter of each successive word is capitalized. | ||
Unless dealing with central libraries (kdecore, kdeui), classes should be in the library namespace. In that case, it is the namespace which starts with K and the classes inside may not start with it. New libraries should choose their namespace. | Unless dealing with central libraries (kdecore, kdeui), classes should be in the library namespace. In that case, it is the namespace which starts with K and the classes inside may not start with it. New libraries should choose their namespace. | ||
Line 18: | Line 20: | ||
This example shows some possible functions names | This example shows some possible functions names | ||
< | <source lang="cpp-qt"> | ||
public: | public: | ||
void setColor(const QColor& c); | void setColor(const QColor& c); | ||
Line 27: | Line 29: | ||
private Q_SLOTS: | private Q_SLOTS: | ||
void slotParentChanged(); | void slotParentChanged(); | ||
</ | </source> | ||
Make one public class for every .h file. Add the <tt>_EXPORT</tt> macro related to the library they are in. | Make one public class for every .h file. Add the <tt>_EXPORT</tt> macro related to the library they are in. | ||
Private classes should be declared in the .cpp file, or in a _p.h file. | Private classes should be declared in the .cpp file, or in a _p.h file. | ||
== D-Pointers == | == D-Pointers == | ||
In order to more easily maintain binary compatibility, there shouldn't be private members in a public class. For more information about binary compatibility, read [[ | In order to more easily maintain binary compatibility, there shouldn't be private members in a public class. For more information about binary compatibility, read [[Policies/Binary_Compatibility_Issues_With_C++|Binary Compatibility Issues With C++]]. | ||
By convention, the private class will be named the same as the public class, with <tt>Private</tt> appended to the name. | By convention, the private class will be named the same as the public class, with <tt>Private</tt> appended to the name. | ||
< | <source lang="cpp-qt"> | ||
class KFooPrivate; | class KFooPrivate; | ||
class KFoo | class KFoo | ||
{ | { | ||
public: | |||
/* public members */ | |||
private: | |||
const QScopedPointer<KFooPrivate> d; | |||
}; | }; | ||
</ | </source> | ||
In the .cpp file: | In the .cpp file: | ||
< | <source lang="cpp-qt"> | ||
class KFooPrivate | class KFooPrivate | ||
{ | { | ||
Line 59: | Line 61: | ||
KFoo::~KFoo() | KFoo::~KFoo() | ||
{ | { | ||
// You must define a non-inline destructor in the .cpp file, even if it is empty | |||
// else, a default one will be built in a place where KFooPrivate is only forward | |||
// declare, leading to an error in the destructor of QScopedPointer | |||
} | } | ||
</ | </source> | ||
Notice that the member d is <tt>const</tt> to avoid modifying it by mistake. | Notice that the member d is <tt>const</tt> to avoid modifying it by mistake. This example also uses {{qt|QScopedPointer}} for the d-pointer. It's not a requirement, you can use just your private class directly, but then you need to take care of deleting it properly in the destructor. {{qt|QScopedPointer}} does that for you automatically, see the docs for more details. | ||
If you are implementing an implicitly shared class, you should consider using {{qt|QSharedData}} and {{qt|QSharedDataPointer}} for d. | If you are implementing an implicitly shared class, you should consider using {{qt|QSharedData}} and {{qt|QSharedDataPointer}} for d. if you do, remember you need to implement explicit constructors, destructors and assignment operators (operator=). | ||
Sometimes, complex code may be moved to a member method of the Private class itself. Doing this may give the compiler an extra register to optimize the code, since you won't be using "d" all the time. | Sometimes, complex code may be moved to a member method of the Private class itself. Doing this may give the compiler an extra register to optimize the code, since you won't be using "d" all the time. Also, remember to '''inline''' such methods if they are called only from one place. | ||
=== Shared D-Pointers === | |||
If your class hierarchy is large and/or deep, you may want to try the concept of shared d-pointers. You'll be trading the added complexity for a smaller memory footprint in the main object (there will be only one "d" variable in it). Other advantages include: | |||
:* direct access to the private data of the whole hierarchy (in other words, the Private classes are in fact "protected", not "private") | |||
:* access to the parent's d-pointer methods | |||
The latter advantage is especially useful if your class has moved the code from the main class to the Private class. If that's the case, you should be calling the Private methods instead: since they are not exported, they will create simpler relocations in the final library (or none at all). By simply calling the Private method instead of the public one, you contribute to a faster load-time of your library. | |||
To implement a "shared d-pointer", you need to: | |||
:# define a '''protected''' variable (d_ptr) in the least derived class of your hierarchy | |||
:# in each class of the hierarchy, define a '''private''' function called d_func() that reinterpret_casts that d_ptr to the current class's Private class | |||
:# use Q_D(Foo) at the beginning of the functions to have access to a variable "d" | |||
:# the private classes derive from one another just like the public hierarchy; they also have virtual destructors | |||
:# add one extra, protected constructor that takes the private class as a parameter | |||
:# in each constructor for all derived classes, call the parent's constructor that takes the d pointer as a parameter | |||
There's an example of such a construct in a [https://techbase.kde.org/Policies/Library_Code_Policy/Shared_D-Pointer_Example separate page]. | |||
=== Q_DECLARE_PRIVATE === | |||
This is a handy macro that hides the ugly stuff for you. It creates the <tt>d_func()</tt> function for you, using the variable called <tt>d_ptr</tt>. If yours has that name, you can use this macro. If it has another name, maybe you should create a macro to make your code look nicer. | |||
=== Q-Pointers === | |||
Q-pointers are like d-pointers, but work in the reverse direction: they are in the Private class and they point to the public class. Needless to say, this is only possible for classes that don't share their d-pointers. Examples of classes that might benefit from q-pointers are all those derived from QObject, while classes with implicit sharing are those that potentially can't use it. | |||
Q-pointers are especially useful if your class has moved most of the code to the Private class as recommended. In that case, you may need to emit signals from the Private class. You would do it as: | |||
<source lang="cpp-qt"> | |||
emit q->signalName(); | |||
</source> | |||
(You need to declare the Private class a friend of your public one; Q_DECLARE_PRIVATE does that for you) | |||
Q-pointers may also use the shared q-pointer technique just like [[#Shared D-Pointers|d-pointers]] can. What's more, Qt also provides a macro called <tt>Q_DECLARE_PUBLIC</tt> and one <tt>Q_Q</tt> to hide the ugly parts of the implementation. | |||
== Inline Code == | == Inline Code == | ||
Line 75: | Line 115: | ||
* Installed headers should compile with the following preprocessor defines: <tt>QT_NO_CAST_FROM_ASCII</tt>, <tt>QT_NO_CAST_TO_ASCII</tt>, <tt>QT_NO_KEYWORD</tt>. So don't forget {{qt|QLatin1String}}. | * Installed headers should compile with the following preprocessor defines: <tt>QT_NO_CAST_FROM_ASCII</tt>, <tt>QT_NO_CAST_TO_ASCII</tt>, <tt>QT_NO_KEYWORD</tt>. So don't forget {{qt|QLatin1String}}. | ||
* No C casts in the header. Use <tt>static_cast</tt> if types are known. Use <tt>qobject_cast</tt> instead of <tt>dynamic_cast</tt> if types are QObject based. dynamic_cast is not only slower, but is also unreliable across shared libraries. | * No C casts in the header. Use <tt>static_cast</tt> if types are known. Use <tt>qobject_cast</tt> instead of <tt>dynamic_cast</tt> if types are QObject based. dynamic_cast is not only slower, but is also unreliable across shared libraries. | ||
* In general, check your code for [ | * In general, check your code for [https://techbase.kde.org/Development/Tutorials/Common_Programming_Mistakes common mistakes]. | ||
These recommendations are also true for code that | These recommendations are also true for code that is not in headers. | ||
== Flags == | == Flags == | ||
Try to avoid meaningless boolean parameters in functions. Example of a bad boolean argument: | Try to avoid meaningless boolean parameters in functions. Example of a bad boolean argument: | ||
<source lang="cpp-qt"> | |||
bool withAppName | static QString KApplication::makeStdCaption( const QString &caption, | ||
bool modified | bool withAppName, | ||
bool modified); | |||
</source> | |||
Because when you read code that uses the above function, you can't easily know the significance of the parameters | Because when you read code that uses the above function, you can't easily know the significance of the parameters | ||
<source lang="cpp-qt"> | |||
window->setCaption(KApplication::makeStdCaption( "Document Foo", | |||
true, true)); | |||
</source> | |||
The solution is to use {{qt|QFlags}}. If the options only apply to one function, call the <tt>enum FunctionNameOption</tt> and the QFlags typedef <tt>FunctionNameOptions</tt>. Do that even if there is only one option, this will allow you to add more options later and keep the binary compatibility. | The solution is to use {{qt|QFlags}}. If the options only apply to one function, call the <tt>enum FunctionNameOption</tt> and the QFlags typedef <tt>FunctionNameOptions</tt>. Do that even if there is only one option, this will allow you to add more options later and keep the binary compatibility. | ||
So a better API would be: | So a better API would be: | ||
< | <source lang="cpp-qt"> | ||
class KApplication | class KApplication | ||
{ | { | ||
public: | |||
/* [...] */ | |||
enum StandardCaptionOption { | |||
/** | |||
* Indicates to include the application name | |||
*/ | |||
WithApplicationName = 0x01, | |||
/** | /** | ||
* | * Note in the caption that there is unsaved data | ||
*/ | */ | ||
static QString makeStandardCaption(const QString& userCaption, | Modified = 0x02 | ||
}; | |||
Q_DECLARE_FLAGS(StandardCaptionOptions, | |||
StandardCaptionOption) | |||
/** | |||
* Builds a caption using a standard layout. | |||
* | |||
* @param userCaption The caption string you want to display | |||
* @param options a set of flags from MakeStandartCaptionOption | |||
*/ | |||
static QString makeStandardCaption(const QString& userCaption, | |||
const StandardCaptionOptions& options = WithApplicationName); | |||
/* [...] */ | |||
}; | }; | ||
Q_DECLARE_OPERATORS_FOR_FLAGS(KApplication:: | Q_DECLARE_OPERATORS_FOR_FLAGS(KApplication::StandardCaptionOptions) | ||
</ | </source> | ||
== Const References == | == Const References == | ||
Each object parameter that is not a basic type (int, float, bool, enum, or pointers) should be passed by reference-to-const. This is faster, because it is not required to do a copy of the object. Do that even for | Each object parameter that is not a basic type (int, float, bool, enum, or pointers) should be passed by reference-to-const. This is faster, because it is not required to do a copy of the object. Do that even for objects that are already implicitly shared, like QString: | ||
<source lang="cpp-qt"> | |||
QString myMethod( const QString& foo, | |||
const QPixmap& bar, | |||
int number ); | |||
</source> | |||
'''Note:''' Avoid const references for return types though. Returning for example | |||
<source lang="cpp-qt"> | |||
const QList<int> &someProperty() const; | |||
</source> | |||
means exposing the internal data structure for someProperty() and it's very difficult to change it in the future while preserving binary compatibility. Especially for implicitly shared objects the one refcount that one avoids by returning a const reference is often not worth it the exposure of implementation. | |||
There are cases where it makes sense, where performance is absolutely critical and the implementation is very fixed. So think twice about it and consider returning a value instead: | |||
<source lang="cpp-qt"> | |||
QList<int> someProperty() const; | |||
</source> | |||
== Signals and Slots == | == Signals and Slots == | ||
Line 128: | Line 192: | ||
== Properties == | == Properties == | ||
Consider using <tt>Q_PROPERTY</tt> for properties. The reason is that properties (especially | Consider using <tt>Q_PROPERTY</tt> for properties. The reason is that properties (especially those marked <tt>SCRIPTABLE</tt>) will be accessible through the javascript interface. | ||
If you follow the propname / setPropname naming | If you follow the propname / setPropname naming scheme, moc sets a special flag for the {{qt|QMetaProperty}}. | ||
== Explicit Constructors == | == Explicit Constructors == | ||
Line 143: | Line 207: | ||
In this example, the class KFoo uses KBar by reference, so we do not need to include KBar's header: | In this example, the class KFoo uses KBar by reference, so we do not need to include KBar's header: | ||
< | <source lang="cpp-qt"> | ||
#include <kfoobase.h> | #include <kfoobase.h> | ||
class KBar; | class KBar; | ||
Line 152: | Line 216: | ||
void myMethod(const KBar& bar); | void myMethod(const KBar& bar); | ||
}; | }; | ||
</ | </source> | ||
== Getting #includes right == | |||
There are two types of #include statements: <tt>#include <foo.h></tt> and <tt>#include "foo.h"</tt>. | |||
Say we have the file <tt>xyz.h</tt> in <tt>/usr/include/mylib/</tt> that contains the following: | |||
<source lang="cpp-qt"> | |||
#include <header1.h> | |||
#include "header2.h" | |||
</source> | |||
The preprocessor will search for the file <tt>header1.h</tt> in all the paths given as <tt>-I</tt> arguments and then replace the line with the contents of that file. | |||
For line 2 the preprocessor tries to use the file /usr/include/mylib/header2.h first and if it does not exist search for the file like it did for <tt>header1.h</tt>. The important part to note here is that the preprocessor does not look in the directory of the source file that includes <tt>xyz.h</tt> but in the directory where <tt>xyz.h</tt> resides. | |||
Now, which include statement is the one to use? After all you can specify every directory you want using <tt>-I</tt> (or rather CMake's <tt>include_directories()</tt>) and thus could use <tt>#include <...></tt> everywhere. | |||
=== As application developer === | |||
* Include headers from '''external''' libraries using '''angle brackets'''. | |||
<source lang="cpp-qt"> | |||
#include <iostream> | |||
#include <QDate> | |||
#include <zlib.h> | |||
</source> | |||
* Include headers from your '''own project''' using '''double quotes'''. | |||
<source lang="cpp-qt"> | |||
#include "myclass.h" | |||
</source> | |||
Rationale: ''The header files of external libraries are obviously not in the same directory as your source files. So you need to use angle brackets.'' | |||
''Headers of your own application have a defined relative location to the source files of your application. Using KDE4's cmake macros your source directory is the first include switch to the compiler and therefore there's no difference in using angle brackets or double quotes. If you work with a different buildsystem that does not include the current source directory or disable CMAKE_INCLUDE_CURRENT_DIR then all includes (inside your application) using angle brackets will break.'' | |||
''Ideally the buildsystem would not need to specify <tt>-I<source directory></tt> though as that can break with library headers that have the same filename as a header of your project (i.e.: If a library has the header file <tt>foo.h</tt> and your project has a different file with the same filename the compiler will always pick the header from your project instead of the one from the library because the source directory of the project is specified first.)'' | |||
=== As library developer === | |||
* Include headers from '''external''' libraries using '''angle brackets'''. | |||
<source lang="cpp-qt"> | |||
#include <iostream> | |||
#include <QDate> | |||
#include <zlib.h> | |||
</source> | |||
* Include headers of your '''own library''' and libraries that belong to it using '''double quotes'''. | |||
<source lang="cpp-qt"> | |||
#include "xyz.h" // same library and same directory | |||
</source> | |||
Rationale: ''The header files of external libraries are obviously not in a fixed location relative to your source files. So you need to use angle brackets.'' | |||
''Headers of your own libraries have a fixed relative location in the filesystem. Therefore you'' can ''use double quotes. You should use double quotes because otherwise the include statement could include a different header file than expected. An example how angle brackets can break the build:'' | |||
''<tt>/usr/include/libxyz/xyz.h</tt> includes <tt>foo.h</tt> using angle brackets and expects to have it replaced with the contents of the file <tt>/usr/include/libzyx/foo.h</tt>. Assuming there's another library that also ships a <tt>foo.h</tt> file in the directory <tt>/usr/include/anotherlib/</tt>. If the application that uses both libraries compiles with "<tt>g++ -I/usr/include/libxyz -I/usr/include/anotherlib ...</tt>" libxyz will work as expected. If the application compiles with "<tt>g++ -I/usr/include/anotherlib -I/usr/include/libxyz ...</tt>" the header <tt>xyz.h</tt> will include the file <tt>/usr/include/anotherlib/foo.h</tt> instead of the file that is shipped with libxyz. The same problem can appear if an application has a header file of the same name as a library and specifies <tt>-I./</tt> as the first include directory.'' | |||
=== Qt includes === | |||
Always include the Qt forwarding class header without it's module as in : | |||
<source lang="cpp-qt"> | |||
#include <QDate> //correct | |||
</source> | |||
<source lang="cpp-qt"> | |||
#include <QtCore/QDate> //incorrect, no need to specify the module QtCore | |||
#include <QtCore> //incorrect, do not include the top-level Qt module | |||
</source> | |||
=== Include order === | |||
Another important aspect of include management is the include order. Typically, you have a class named Foo, a file foo.h and a file foo.cpp . The rule is : | |||
: ''In your file foo.cpp, you should include "foo.h" as the first include, before the system includes.'' | |||
The rationale behind that is to make your header standalone. | |||
Let's imagine that your foo.h looks like this: | |||
<source lang="cpp-qt"> | |||
class Foo | |||
{ | |||
public: | |||
Bar getBar(); | |||
}; | |||
</source> | |||
And your foo.cpp looks like this: | |||
<source lang="cpp-qt"> | |||
#include "bar.h" | |||
#include "foo.h" | |||
</source> | |||
Your foo.cpp file will compile, but it will not compile for other people using foo.h without including bar.h . Including "foo.h" first makes sure that your foo.h header works for others. | |||
=== Include guards === | |||
Header files should use guards to protect against possible multiple inclusion. | |||
Your myfoo.h header should look like this: | |||
<source lang="cpp-qt"> | |||
#ifndef MYFOO_H | |||
#define MYFOO_H | |||
... <stuff>... | |||
#endif /* MYFOO_H */ | |||
</source> | |||
To be even more careful, you may want to encode a namespace or subdirectory name (e.g. KFOO) into the guard macro name, for example: MYFOO_H, KFOO_MYFOO_H, or _KFOO_MYFOO_H_ are all acceptable macro names. By convention, the macro name should be all uppercase; but that is not a firm requirement. | |||
== Static Objects == | == Static Objects == | ||
Global static objects in libraries should be avoided. You never know when the constructor will be run or if it will be run at all. | Global static objects in libraries should be avoided. You never know when the constructor will be run or if it will be run at all. | ||
; Wrong | ; Wrong | ||
< | <source lang="cpp-qt"> | ||
static QString foo; // wrong - object might not be constructed | static QString foo; // wrong - object might not be constructed | ||
static QString bar("hello"); // as above | static QString bar("hello"); // as above | ||
static int foo = myInitializer(); // myInitializer() might not be called | static int foo = myInitializer(); // myInitializer() might not be called | ||
</ | </source> | ||
In particular, <b>never</b> construct QObject-derived objects this way. On Windows (MS Visual C++) the object's internals will be left uninitialized within a library and will lead to crashes ([http://www.kdedevelopers.org/node/2889 more info]). | |||
<source lang="cpp-qt"> | |||
static QFile myFile("abc"); // QFile inherits QObject | |||
</source> | |||
Also, when using KUniqueApplication and using static objects that may query DBus during their initialization, the application can dead-lock. | |||
; Correct | ; Correct | ||
< | <source lang="cpp-qt"> | ||
static const int i = 42; | static const int i = 42; | ||
static const int ii[3] = {1, 2, 3}; | static const int ii[3] = {1, 2, 3}; | ||
static const char myString[] = "hello"; | static const char myString[] = "hello"; | ||
static const MyStruct s = {3, 4.4, "hello"}; | static const MyStruct s = {3, 4.4, "hello"}; | ||
</ | Q_GLOBAL_STATIC(QFile, myFile); | ||
</source> | |||
You can use <tt>Q_GLOBAL_STATIC</tt> to create global static objects which will be initialized the first time you use them. | You can use [https://doc.qt.io/qt-5/qglobalstatic.html <tt>Q_GLOBAL_STATIC</tt>] macro (and for QObject-derived objects you should) to create global static objects which will be initialized the first time you use them. | ||
== Signal and Slot Normalization == | == Signal and Slot Normalization == | ||
Line 180: | Line 351: | ||
For example, you may have the following code: | For example, you may have the following code: | ||
< | <source lang="cpp-qt"> | ||
QObject::connect(this, SIGNAL( newValue(const QString&, | QObject::connect(this, SIGNAL( newValue(const QString&, | ||
const MyNamespace::Type&) ), | const MyNamespace::Type&) ), | ||
other, SLOT( value(const QString &) )); | other, SLOT( value(const QString &) )); | ||
</ | </source> | ||
It would be | It would be preferable to write as follows: | ||
< | <source lang="cpp-qt"> | ||
QObject::connect(this, SIGNAL(newValue(QString,Type)), | QObject::connect(this, SIGNAL(newValue(QString,MyNamespace::Type)), | ||
other, SLOT(value(QString))); | other, SLOT(value(QString))); | ||
</ | </source> | ||
Note the absence of | Note the absence of extra whitespace and the | ||
reduction of pass-by-reference-to-const parameters to simple | reduction of pass-by-reference-to-const parameters to simple | ||
pass-by-value ones. The normalization may involve other | pass-by-value ones. The normalization may involve other | ||
transformations, but these are the most common ones. To be sure what | transformations, but these are the most common ones. Also note that | ||
the proper normalization is, read the {{path|.moc}} file generated | types in namespaces should always use the full qualified name. | ||
for the class. | To be sure what the proper normalization is, read the {{path|.moc}} file | ||
generated for the class. | |||
'''Note''': If you are unsure about the normalization, don't do it. Let | '''Note''': If you are unsure about the normalization, don't do it. Let | ||
QObject do it for you (the performance penalty is negligible in most cases). | QObject do it for you (the performance penalty is negligible in most cases). | ||
== External Dependencies == | |||
When a library pulls in a new external dependency due to a new feature or a change in implementation, it is highly preferred to make that dependency optional if at all possible. For some core features in a library this may not be possible, but usually it is even if the cost is a degradation in features when built without that dependency. | |||
All dependencies should be adequately documented in the build system so that they appear in the summary post-configure. | |||
For libraries in the '''kdelibs module''', this is a hard requirement: '''no new non-optional dependencies may be added, all new dependencies must be made optional at configure time'''. Exceptions may be granted for unusual circumstances by sending a request to the kde-core-devel at kde.org mailing list and getting a consensus decision to grant an exception there. | |||
== Documentation == | == Documentation == | ||
Line 206: | Line 384: | ||
Also don't forget the license headers and copyrights in each file. As stated in the [[Policies/Licensing Policy|Licensing Policy]], kdelibs code must be licensed under the LGPL, BSD, or X11 license. | Also don't forget the license headers and copyrights in each file. As stated in the [[Policies/Licensing Policy|Licensing Policy]], kdelibs code must be licensed under the LGPL, BSD, or X11 license. | ||
== Deprecation of API == | |||
If an item of the API is no longer recommended for use, usually with a better replacement, it gets tagged as such both in the API documentation as well for the compiler (with C++ and the current tools that data duplication is needed), noting the very version the item got deprecated and what recommended replacement there is, if. | |||
For example, you may have the following definition of a function which is published: | |||
<source lang="cpp-qt"> | |||
#include <foo_export.h> | |||
FOO_EXPORT void foo(); | |||
</source> | |||
Where "FOO_EXPORT" is the macro holding the visibility attribute, as defined in the included file "foo_export.h". That file usually is generated with a macro from CMake's [https://cmake.org/cmake/help/latest/module/GenerateExportHeader.html GenerateExportHeader] or KDE's enhanced variant of it, [https://api.kde.org/ecm/module/ECMGenerateExportHeader.html ECMGenerateExportHeader] | |||
When using export macro headers generated by ECMGenerateExportHeader, one would use the version-based macro "FOO_DEPRECATED_VERSION" and additionally the "FOO_ENABLE_DEPRECATED_SINCE" macro, like this (no combined macro available here, and duplication of data for the different tools also needed here): | |||
<source lang="cpp-qt"> | |||
#include <foo_export.h> | |||
#if FOO_ENABLE_DEPRECATED_SINCE(5, 0) | |||
/** | |||
* @deprecated Since 5.0. Use bar(). | |||
*/ | |||
FOO_EXPORT | |||
FOO_DEPRECATED_VERSION(5, 0, "Use bar()") | |||
void foo(); | |||
#endif | |||
</source> | |||
The "FOO_BUILD_DEPRECATED_SINCE" macro can control compilation of the | |||
implementation: | |||
<source lang="cpp-qt"> | |||
#include "foo.h" | |||
#if FOO_BUILD_DEPRECATED_SINCE(5, 0) | |||
void foo() | |||
{ | |||
} | |||
#endif | |||
</source> | |||
{{Note|Keep any export macro before the deprecation macro or other attribute macros.}} | |||
{{Note|Make sure that the version at which the deprecation happened is listed with the <tt>DEPRECATION_VERSIONS</tt> argument of the <tt>ecm_generate_export_header</tt> call.}} | |||
The API documenation generation tool need some support to correctly handle these macros. With [https://api.kde.org/ecm/module/ECMAddQch.html ECMAddQch], the <tt>ecm_add_qch</tt> call should includes this: | |||
<source lang="cmake"> | |||
ecm_add_qch(FOO_QCH | |||
# [...] | |||
INCLUDE_DIRS | |||
# [...] | |||
/path/to/dir/where/generated/export/header/is/found | |||
BLANK_MACROS | |||
# [...] | |||
"FOO_DEPRECATED_VERSION(x, y, t)" | |||
"FOO_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)" | |||
"FOO_ENUMERATOR_DEPRECATED_VERSION(x, y, t)" | |||
"FOO_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)" | |||
) | |||
</source> | |||
With [https://api.kde.org/frameworks/kapidox/html/index.html KApidox], the file "docs/Doxyfile.local" should have an entry like this: | |||
<source lang="doxygen"> | |||
PREDEFINED += \ | |||
"FOO_ENABLE_DEPRECATED_SINCE(x, y)=1" \ | |||
"FOO_BUILD_DEPRECATED_SINCE(x, y)=1" \ | |||
"FOO_DEPRECATED_VERSION(x, y, t)=" \ | |||
"FOO_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \ | |||
"FOO_ENUMERATOR_DEPRECATED_VERSION(x, y, t)=" \ | |||
"FOO_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" | |||
</source> | |||
When using GenerateExportHeader, the export header file provides a macro "FOO_DEPRECATED" to do that for the compiler, as well as a convenience macro "FOO_EXPORT_DEPRECATED" which covers both visibility & deprecation attributes, for shorter code: | |||
<source lang="cpp-qt"> | |||
#include <foo_export.h> | |||
/** | |||
* @deprecated Since 5.0. Use bar(). | |||
*/ | |||
FOO_EXPORT_DEPRECATED void foo(); | |||
</source> | |||
Again the API documenation generation tool need some support. With ECMAddQch, the <tt>ecm_add_qch</tt> call should includes this: | |||
<source lang="cmake"> | |||
ecm_add_qch(FOO_QCH | |||
# [...] | |||
INCLUDE_DIRS | |||
# [...] | |||
/path/to/dir/where/generated/export/header/is/found | |||
BLANK_MACROS | |||
FOO_EXPORT | |||
FOO_DEPRECATED | |||
FOO_DEPRECATED_EXPORT | |||
) | |||
</source> | |||
With KApidox, the file "docs/Doxyfile.local" should have an entry like this: | |||
<source lang="doxygen"> | |||
PREDEFINED += \ | |||
"FOO_EXPORT=" \ | |||
"FOO_DEPRECATED=" \ | |||
"FOO_EXPORT_DEPRECATED=" | |||
</source> | |||
== <tt>auto</tt> Keyword == | |||
Optionally, you can use the <tt>auto</tt> keyword in the following cases. If in doubt, for example if using <tt>auto</tt> could make the code less readable, do not use <tt>auto</tt>. Keep in mind that code is read much more often than written. | |||
* When it avoids repetition of a type in the same statement. | |||
<source lang="cpp-qt"> | |||
auto something = new MyCustomType; | |||
auto keyEvent = static_cast<QKeyEvent *>(event); | |||
auto myList = QStringList({ "FooThing", "BarThing" }); | |||
</source> | |||
* When assigning iterator types. | |||
<source lang="cpp-qt"> | |||
auto it = myList.const_iterator(); | |||
</source> | |||
Author: [mailto:[email protected] Olivier Goffart] March 2006 | Author: [mailto:[email protected] Olivier Goffart] March 2006 | ||
[[Category:Policies]] | [[Category:Policies]] |
Latest revision as of 18:24, 13 August 2022
This document describes some of the recommended conventions that should be applied in the KDE libraries (not applications). Respecting these guidelines helps create a consistent API and also may help ease maintainence of the libraries later. While these conventions are not mandatory, they are important guidelines, and should be respected unless you have a good reason to disregard them.
As an introduction, you should read the document Qt-Style C++ API Design Principles.
For KDE Frameworks, it is recommended to follow the KDE Frameworks Coding Style.
Naming Conventions
In KDE, we basically follow the same naming conventions as Qt.
Class names start with a capital K. The rest is in camel case. Function names start with a lower case, but the first letter of each successive word is capitalized.
Unless dealing with central libraries (kdecore, kdeui), classes should be in the library namespace. In that case, it is the namespace which starts with K and the classes inside may not start with it. New libraries should choose their namespace.
The prefix 'set' is used for setters, but the prefix 'get' is not used for accessors. Accessors are simply named with the name of the property they access. The exception is for accessors of a boolean which may start with the prefix 'is'.
Acronyms are lowercased too. Example: KUrl instead of KURL and isNssEnabled() instead of isNSSEnabled()
Accessors should usually be const.
This example shows some possible functions names
public:
void setColor(const QColor& c);
QColor color() const;
void setDirty(bool b);
bool isDirty() const;
private Q_SLOTS:
void slotParentChanged();
Make one public class for every .h file. Add the _EXPORT macro related to the library they are in. Private classes should be declared in the .cpp file, or in a _p.h file.
D-Pointers
In order to more easily maintain binary compatibility, there shouldn't be private members in a public class. For more information about binary compatibility, read Binary Compatibility Issues With C++.
By convention, the private class will be named the same as the public class, with Private appended to the name.
class KFooPrivate;
class KFoo
{
public:
/* public members */
private:
const QScopedPointer<KFooPrivate> d;
};
In the .cpp file:
class KFooPrivate
{
public:
int someInteger;
};
KFoo::KFoo() : d(new KFooPrivate)
{
/* ... */
}
KFoo::~KFoo()
{
// You must define a non-inline destructor in the .cpp file, even if it is empty
// else, a default one will be built in a place where KFooPrivate is only forward
// declare, leading to an error in the destructor of QScopedPointer
}
Notice that the member d is const to avoid modifying it by mistake. This example also uses QScopedPointer for the d-pointer. It's not a requirement, you can use just your private class directly, but then you need to take care of deleting it properly in the destructor. QScopedPointer does that for you automatically, see the docs for more details.
If you are implementing an implicitly shared class, you should consider using QSharedData and QSharedDataPointer for d. if you do, remember you need to implement explicit constructors, destructors and assignment operators (operator=).
Sometimes, complex code may be moved to a member method of the Private class itself. Doing this may give the compiler an extra register to optimize the code, since you won't be using "d" all the time. Also, remember to inline such methods if they are called only from one place.
If your class hierarchy is large and/or deep, you may want to try the concept of shared d-pointers. You'll be trading the added complexity for a smaller memory footprint in the main object (there will be only one "d" variable in it). Other advantages include:
- direct access to the private data of the whole hierarchy (in other words, the Private classes are in fact "protected", not "private")
- access to the parent's d-pointer methods
The latter advantage is especially useful if your class has moved the code from the main class to the Private class. If that's the case, you should be calling the Private methods instead: since they are not exported, they will create simpler relocations in the final library (or none at all). By simply calling the Private method instead of the public one, you contribute to a faster load-time of your library.
To implement a "shared d-pointer", you need to:
- define a protected variable (d_ptr) in the least derived class of your hierarchy
- in each class of the hierarchy, define a private function called d_func() that reinterpret_casts that d_ptr to the current class's Private class
- use Q_D(Foo) at the beginning of the functions to have access to a variable "d"
- the private classes derive from one another just like the public hierarchy; they also have virtual destructors
- add one extra, protected constructor that takes the private class as a parameter
- in each constructor for all derived classes, call the parent's constructor that takes the d pointer as a parameter
There's an example of such a construct in a separate page.
Q_DECLARE_PRIVATE
This is a handy macro that hides the ugly stuff for you. It creates the d_func() function for you, using the variable called d_ptr. If yours has that name, you can use this macro. If it has another name, maybe you should create a macro to make your code look nicer.
Q-Pointers
Q-pointers are like d-pointers, but work in the reverse direction: they are in the Private class and they point to the public class. Needless to say, this is only possible for classes that don't share their d-pointers. Examples of classes that might benefit from q-pointers are all those derived from QObject, while classes with implicit sharing are those that potentially can't use it.
Q-pointers are especially useful if your class has moved most of the code to the Private class as recommended. In that case, you may need to emit signals from the Private class. You would do it as:
emit q->signalName();
(You need to declare the Private class a friend of your public one; Q_DECLARE_PRIVATE does that for you)
Q-pointers may also use the shared q-pointer technique just like d-pointers can. What's more, Qt also provides a macro called Q_DECLARE_PUBLIC and one Q_Q to hide the ugly parts of the implementation.
Inline Code
For binary compatibility reasons, try to avoid inline code in headers. Specifically no inline constructor or destructor.
If ever you add inline code please note the following:
- Installed headers should compile with the following preprocessor defines: QT_NO_CAST_FROM_ASCII, QT_NO_CAST_TO_ASCII, QT_NO_KEYWORD. So don't forget QLatin1String.
- No C casts in the header. Use static_cast if types are known. Use qobject_cast instead of dynamic_cast if types are QObject based. dynamic_cast is not only slower, but is also unreliable across shared libraries.
- In general, check your code for common mistakes.
These recommendations are also true for code that is not in headers.
Flags
Try to avoid meaningless boolean parameters in functions. Example of a bad boolean argument:
static QString KApplication::makeStdCaption( const QString &caption,
bool withAppName,
bool modified);
Because when you read code that uses the above function, you can't easily know the significance of the parameters
window->setCaption(KApplication::makeStdCaption( "Document Foo",
true, true));
The solution is to use QFlags. If the options only apply to one function, call the enum FunctionNameOption and the QFlags typedef FunctionNameOptions. Do that even if there is only one option, this will allow you to add more options later and keep the binary compatibility.
So a better API would be:
class KApplication
{
public:
/* [...] */
enum StandardCaptionOption {
/**
* Indicates to include the application name
*/
WithApplicationName = 0x01,
/**
* Note in the caption that there is unsaved data
*/
Modified = 0x02
};
Q_DECLARE_FLAGS(StandardCaptionOptions,
StandardCaptionOption)
/**
* Builds a caption using a standard layout.
*
* @param userCaption The caption string you want to display
* @param options a set of flags from MakeStandartCaptionOption
*/
static QString makeStandardCaption(const QString& userCaption,
const StandardCaptionOptions& options = WithApplicationName);
/* [...] */
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KApplication::StandardCaptionOptions)
Const References
Each object parameter that is not a basic type (int, float, bool, enum, or pointers) should be passed by reference-to-const. This is faster, because it is not required to do a copy of the object. Do that even for objects that are already implicitly shared, like QString:
QString myMethod( const QString& foo,
const QPixmap& bar,
int number );
Note: Avoid const references for return types though. Returning for example
const QList<int> &someProperty() const;
means exposing the internal data structure for someProperty() and it's very difficult to change it in the future while preserving binary compatibility. Especially for implicitly shared objects the one refcount that one avoids by returning a const reference is often not worth it the exposure of implementation.
There are cases where it makes sense, where performance is absolutely critical and the implementation is very fixed. So think twice about it and consider returning a value instead:
QList<int> someProperty() const;
Signals and Slots
In the libraries, use Q_SIGNALS and Q_SLOTS instead of signals and slots. They are syntactically equivalent and should be used to avoid conflicts with boost signals, and with python's use of "slots" in its headers.
Properties
Consider using Q_PROPERTY for properties. The reason is that properties (especially those marked SCRIPTABLE) will be accessible through the javascript interface.
If you follow the propname / setPropname naming scheme, moc sets a special flag for the QMetaProperty.
Explicit Constructors
For each constructor (other than the copy constructor), check if you should make the constructor explicit in order to minimize wrong use of the constructor.
Basically, each constructor that may take only one argument should be marked explicit unless the whole point of the constructor is to allow implicit casting.
Avoid including other headers in headers
Try to reduce as much as possible the number of includes in header files. This will generally help reduce the compilation time, especially for developers when just one header has been modified. It may also avoid errors that can be caused by conflicts between headers.
If an object in the class is only used by pointer or by reference, it is not required to include the header for that object. Instead, just add a forward declaration before the class.
In this example, the class KFoo uses KBar by reference, so we do not need to include KBar's header:
#include <kfoobase.h>
class KBar;
class KFoo : public KFooBase
{
public:
/* [...] */
void myMethod(const KBar& bar);
};
Getting #includes right
There are two types of #include statements: #include <foo.h> and #include "foo.h".
Say we have the file xyz.h in /usr/include/mylib/ that contains the following:
#include <header1.h>
#include "header2.h"
The preprocessor will search for the file header1.h in all the paths given as -I arguments and then replace the line with the contents of that file.
For line 2 the preprocessor tries to use the file /usr/include/mylib/header2.h first and if it does not exist search for the file like it did for header1.h. The important part to note here is that the preprocessor does not look in the directory of the source file that includes xyz.h but in the directory where xyz.h resides.
Now, which include statement is the one to use? After all you can specify every directory you want using -I (or rather CMake's include_directories()) and thus could use #include <...> everywhere.
As application developer
- Include headers from external libraries using angle brackets.
#include <iostream>
#include <QDate>
#include <zlib.h>
- Include headers from your own project using double quotes.
#include "myclass.h"
Rationale: The header files of external libraries are obviously not in the same directory as your source files. So you need to use angle brackets.
Headers of your own application have a defined relative location to the source files of your application. Using KDE4's cmake macros your source directory is the first include switch to the compiler and therefore there's no difference in using angle brackets or double quotes. If you work with a different buildsystem that does not include the current source directory or disable CMAKE_INCLUDE_CURRENT_DIR then all includes (inside your application) using angle brackets will break.
Ideally the buildsystem would not need to specify -I<source directory> though as that can break with library headers that have the same filename as a header of your project (i.e.: If a library has the header file foo.h and your project has a different file with the same filename the compiler will always pick the header from your project instead of the one from the library because the source directory of the project is specified first.)
As library developer
- Include headers from external libraries using angle brackets.
#include <iostream>
#include <QDate>
#include <zlib.h>
- Include headers of your own library and libraries that belong to it using double quotes.
#include "xyz.h" // same library and same directory
Rationale: The header files of external libraries are obviously not in a fixed location relative to your source files. So you need to use angle brackets.
Headers of your own libraries have a fixed relative location in the filesystem. Therefore you can use double quotes. You should use double quotes because otherwise the include statement could include a different header file than expected. An example how angle brackets can break the build:
/usr/include/libxyz/xyz.h includes foo.h using angle brackets and expects to have it replaced with the contents of the file /usr/include/libzyx/foo.h. Assuming there's another library that also ships a foo.h file in the directory /usr/include/anotherlib/. If the application that uses both libraries compiles with "g++ -I/usr/include/libxyz -I/usr/include/anotherlib ..." libxyz will work as expected. If the application compiles with "g++ -I/usr/include/anotherlib -I/usr/include/libxyz ..." the header xyz.h will include the file /usr/include/anotherlib/foo.h instead of the file that is shipped with libxyz. The same problem can appear if an application has a header file of the same name as a library and specifies -I./ as the first include directory.
Qt includes
Always include the Qt forwarding class header without it's module as in :
#include <QDate> //correct
#include <QtCore/QDate> //incorrect, no need to specify the module QtCore
#include <QtCore> //incorrect, do not include the top-level Qt module
Include order
Another important aspect of include management is the include order. Typically, you have a class named Foo, a file foo.h and a file foo.cpp . The rule is :
- In your file foo.cpp, you should include "foo.h" as the first include, before the system includes.
The rationale behind that is to make your header standalone.
Let's imagine that your foo.h looks like this:
class Foo
{
public:
Bar getBar();
};
And your foo.cpp looks like this:
#include "bar.h"
#include "foo.h"
Your foo.cpp file will compile, but it will not compile for other people using foo.h without including bar.h . Including "foo.h" first makes sure that your foo.h header works for others.
Include guards
Header files should use guards to protect against possible multiple inclusion. Your myfoo.h header should look like this:
#ifndef MYFOO_H
#define MYFOO_H
... <stuff>...
#endif /* MYFOO_H */
To be even more careful, you may want to encode a namespace or subdirectory name (e.g. KFOO) into the guard macro name, for example: MYFOO_H, KFOO_MYFOO_H, or _KFOO_MYFOO_H_ are all acceptable macro names. By convention, the macro name should be all uppercase; but that is not a firm requirement.
Static Objects
Global static objects in libraries should be avoided. You never know when the constructor will be run or if it will be run at all.
- Wrong
static QString foo; // wrong - object might not be constructed
static QString bar("hello"); // as above
static int foo = myInitializer(); // myInitializer() might not be called
In particular, never construct QObject-derived objects this way. On Windows (MS Visual C++) the object's internals will be left uninitialized within a library and will lead to crashes (more info).
static QFile myFile("abc"); // QFile inherits QObject
Also, when using KUniqueApplication and using static objects that may query DBus during their initialization, the application can dead-lock.
- Correct
static const int i = 42;
static const int ii[3] = {1, 2, 3};
static const char myString[] = "hello";
static const MyStruct s = {3, 4.4, "hello"};
Q_GLOBAL_STATIC(QFile, myFile);
You can use Q_GLOBAL_STATIC macro (and for QObject-derived objects you should) to create global static objects which will be initialized the first time you use them.
Signal and Slot Normalization
Since QObject::connect uses a string-based comparison of the function signature, it requires some normalization to take place. It does that automatically for you, but it takes some CPU time, so, if it doesn't hurt your code's readability, normalize manually your SIGNAL and SLOT entries.
For example, you may have the following code:
QObject::connect(this, SIGNAL( newValue(const QString&,
const MyNamespace::Type&) ),
other, SLOT( value(const QString &) ));
It would be preferable to write as follows:
QObject::connect(this, SIGNAL(newValue(QString,MyNamespace::Type)),
other, SLOT(value(QString)));
Note the absence of extra whitespace and the reduction of pass-by-reference-to-const parameters to simple pass-by-value ones. The normalization may involve other transformations, but these are the most common ones. Also note that types in namespaces should always use the full qualified name. To be sure what the proper normalization is, read the .moc file generated for the class.
Note: If you are unsure about the normalization, don't do it. Let QObject do it for you (the performance penalty is negligible in most cases).
External Dependencies
When a library pulls in a new external dependency due to a new feature or a change in implementation, it is highly preferred to make that dependency optional if at all possible. For some core features in a library this may not be possible, but usually it is even if the cost is a degradation in features when built without that dependency.
All dependencies should be adequately documented in the build system so that they appear in the summary post-configure.
For libraries in the kdelibs module, this is a hard requirement: no new non-optional dependencies may be added, all new dependencies must be made optional at configure time. Exceptions may be granted for unusual circumstances by sending a request to the kde-core-devel at kde.org mailing list and getting a consensus decision to grant an exception there.
Documentation
Every class and method should be well documented. Read the KDE Library Documentation Policy for the guidelines to follow when documenting your code.
Also don't forget the license headers and copyrights in each file. As stated in the Licensing Policy, kdelibs code must be licensed under the LGPL, BSD, or X11 license.
Deprecation of API
If an item of the API is no longer recommended for use, usually with a better replacement, it gets tagged as such both in the API documentation as well for the compiler (with C++ and the current tools that data duplication is needed), noting the very version the item got deprecated and what recommended replacement there is, if.
For example, you may have the following definition of a function which is published:
#include <foo_export.h>
FOO_EXPORT void foo();
Where "FOO_EXPORT" is the macro holding the visibility attribute, as defined in the included file "foo_export.h". That file usually is generated with a macro from CMake's GenerateExportHeader or KDE's enhanced variant of it, ECMGenerateExportHeader
When using export macro headers generated by ECMGenerateExportHeader, one would use the version-based macro "FOO_DEPRECATED_VERSION" and additionally the "FOO_ENABLE_DEPRECATED_SINCE" macro, like this (no combined macro available here, and duplication of data for the different tools also needed here):
#include <foo_export.h>
#if FOO_ENABLE_DEPRECATED_SINCE(5, 0)
/**
* @deprecated Since 5.0. Use bar().
*/
FOO_EXPORT
FOO_DEPRECATED_VERSION(5, 0, "Use bar()")
void foo();
#endif
The "FOO_BUILD_DEPRECATED_SINCE" macro can control compilation of the implementation:
#include "foo.h"
#if FOO_BUILD_DEPRECATED_SINCE(5, 0)
void foo()
{
}
#endif
The API documenation generation tool need some support to correctly handle these macros. With ECMAddQch, the ecm_add_qch call should includes this:
ecm_add_qch(FOO_QCH
# [...]
INCLUDE_DIRS
# [...]
/path/to/dir/where/generated/export/header/is/found
BLANK_MACROS
# [...]
"FOO_DEPRECATED_VERSION(x, y, t)"
"FOO_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
"FOO_ENUMERATOR_DEPRECATED_VERSION(x, y, t)"
"FOO_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
)
With KApidox, the file "docs/Doxyfile.local" should have an entry like this:
PREDEFINED += \
"FOO_ENABLE_DEPRECATED_SINCE(x, y)=1" \
"FOO_BUILD_DEPRECATED_SINCE(x, y)=1" \
"FOO_DEPRECATED_VERSION(x, y, t)=" \
"FOO_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)=" \
"FOO_ENUMERATOR_DEPRECATED_VERSION(x, y, t)=" \
"FOO_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)="
When using GenerateExportHeader, the export header file provides a macro "FOO_DEPRECATED" to do that for the compiler, as well as a convenience macro "FOO_EXPORT_DEPRECATED" which covers both visibility & deprecation attributes, for shorter code:
#include <foo_export.h>
/**
* @deprecated Since 5.0. Use bar().
*/
FOO_EXPORT_DEPRECATED void foo();
Again the API documenation generation tool need some support. With ECMAddQch, the ecm_add_qch call should includes this:
ecm_add_qch(FOO_QCH
# [...]
INCLUDE_DIRS
# [...]
/path/to/dir/where/generated/export/header/is/found
BLANK_MACROS
FOO_EXPORT
FOO_DEPRECATED
FOO_DEPRECATED_EXPORT
)
With KApidox, the file "docs/Doxyfile.local" should have an entry like this:
PREDEFINED += \
"FOO_EXPORT=" \
"FOO_DEPRECATED=" \
"FOO_EXPORT_DEPRECATED="
auto Keyword
Optionally, you can use the auto keyword in the following cases. If in doubt, for example if using auto could make the code less readable, do not use auto. Keep in mind that code is read much more often than written.
- When it avoids repetition of a type in the same statement.
auto something = new MyCustomType;
auto keyEvent = static_cast<QKeyEvent *>(event);
auto myList = QStringList({ "FooThing", "BarThing" });
- When assigning iterator types.
auto it = myList.const_iterator();
Author: Olivier Goffart March 2006