23.3. Private Interface Example

Here we return to our user trees in doc/example/general/user. In our user branch, we have modified the project-lib library to make use of private interfaces. If you look at the Abuild.conf in the src directory, you will see that it lists private in its supported-flags key:

general/user/project/lib/src/Abuild.conf

name: project-lib.src
platform-types: native
deps: common-lib1
supported-flags: private

In its Abuild.interface file, it adds ../private-include to INCLUDES only when the private flag is set:

general/user/project/lib/src/Abuild.interface

INCLUDES = ../include
flag private INCLUDES = ../private-include
LIBDIRS = $(ABUILD_OUTPUT_DIR)
LIBS = project-lib

This makes the headers in the private-include directory visible to it and any build item that depends on it with -flag=private. [47] Note that the project-lib.src build item didn't have to do anything special to see its own private interfaces. This is because a build item automatically operates with all of its own interface flags set for itself. Another thing we've done in this build item is to put the new source file ProjectLib_private.cpp in a private subdirectory:

general/user/project/lib/src/Abuild.mk

TARGETS_lib := project-lib
SRCS_lib_project-lib := \
        ProjectLib.cpp \
        private/ProjectLib_private.cpp
RULES := ccxx

The only reason we did this was to demonstrate that abuild allows multi-element paths (i.e., paths with subdirectories in them) in your source variables. Just avoid putting “..” anywhere in the path. [48]

If you study ProjectLib.cpp in user/project/lib/src, you will notice that we have included the file ProjectLib_private.hpp, which is located in the private-include directory, and that we have called a function that is declared in that file to get the value with which we initialize cl1:

general/user/project/lib/private-include/ProjectLib_private.hpp

#ifndef __PROJECTLIB_PRIVATE_HPP__
#define __PROJECTLIB_PRIVATE_HPP__

extern int ProjectLib_private_get_value();
extern void ProjectLib_private_set_value(int);

#endif // __PROJECTLIB_PRIVATE_HPP__

general/user/project/lib/src/ProjectLib.cpp

#include "ProjectLib.hpp"
#include "ProjectLib_private.hpp"

#include <iostream>

ProjectLib::ProjectLib() :
    cl1(ProjectLib_private_get_value())
{
}

void
ProjectLib::hello()
{
    this->cl1.countBackwards();
}

Private interfaces can be particularly useful in any implementation that hides implementation details from outside users because it can prevent accidentally accessing restricted header files. This type of construct is most useful in straight C code rather than C++ code since C doesn't provide any encapsulation capability other than use of opaque types defined in private header files. This is somewhat akin to using the friend keyword in C++, except that access to private interfaces is requested by the accessor rather than the accessee. [49]

The test code in main.cpp in user/project/lib/test also calls a function defined in the private header file. Note that the Abuild.conf file in the test directory mentions project-lib.src explicitly in its dependency list, and that it is followed by -flag=private:

general/user/project/lib/test/Abuild.conf

name: project-lib.test
platform-types: native
deps: project-lib project-lib.src -flag=private
traits: interesting tester -item=project-lib.src

This means that when project-lib.test reads project-lib.src's Abuild.interface file, any assignments that are flagged with the private flag will be processed.

The alert reader may notice that we have also assigned the trait interesting to this build item. Although the build item is somewhat interesting, the primary purpose of doing this is to illustrate the use of a trait without a referent build item and to show how a trait can be added in a specific tree to supplement traits that are available because of our tree dependencies.



[47] Note that when the private flag is set, both assignments to INCLUDES take effect. To understand why this is, please see Section 33.7, “Implementation of the Abuild Interface System”.

[48] Such constructs, if permitted, would potentially cause abuild to write files outside the output directory. For example, if you had ../A.cc as a source file, abuild would construct abuild-platform/../A.o as the object file name. Fortunately, abuild actually detects this case and reports an error.

[49] Since the build item that supports the private flag is also protected by the scope of its name, this gives us an added layer of protection.