C++ Project Structure – Organizing Source Files/Classes in Big Projects

cproject-structure

I'm currently writing a bigger program in C++ and have reached a point were I get problems with the organization. I wonder if namespaces are a way out.

I will try to explain what the program does and show a simplified example of the mess I'm in with it.

Part of the program configures so-called Devices. Each Device needs to be configured differently.

So basically the following is done:

  • Read in existing Devices. The read devices needs to be analyzed to determine which Device type they are. The class Devices does this. It sorts the Devices into the types Device_type10_valve Device_type20_drive and so on…

  • From another source I get raw data which needs to be analyzed and gets collected into the right device type. I manage all this in class Devices_config_data_collection.

  • Why do I not configure them directly in the Devices? Because I need to collect several of these data inputs together since they can have dependencies with each other (e.g. two containers of Device_config_data_type10_valve get merged into one Device_config_data_type10_valve).

  • The Data of Devices_config_data_collection gets added to the Devices

  • Devices with the configured data get written out to the source with the configured data.

To achieve this I have the following class hierarchy in pseudo C++ code

namespace Application_name {    // for all the methods in the program

    //Device_config_data_type_base.h   // Device_config_data_type_base.cpp
    class Device_config_data_type_base {
        //..
    };

    //Device_config_data_type10_valve.h   // Device_config_data_type10_valve.cpp
    class Device_config_data_type10_valve : public Device_config_data_type_base {
        //...
    };

    //Device_config_data_type20_drive.h   // Device_config_data_type20_drive_valve.cpp

    class Device_config_data_type20_drive : public Device_config_data_type_base {
        //...
    };

    //.... more derived classes like this from Device_config_data_base


    // Devices_data_collection.h    // Devices_data_collection.cpp
    class Devices_config_data_collection {
    public:
        // put in raw data and make a collection of config data
    private:
        struct data {
            std::vector<Device_config_data_type10_valve> type10;
            std::vector<Device_config_data_type20_drive> type20;
            //... and more
        };

        // more stuff also sorts in valid and invalid inputs
    };


    // Devices_type_base.h // Devices_type_base.cpp
    class Device_type_base {
        //..
    };

    // Device_type10_valve.h // Device_type10_valve.cpp
    class Device_type10_valve : public Device_type_base {
        //..

        void insert_config_data(const Device_config_data_type10_valve& data);
    private:
        // part of the private data is the config data

        Device_config_data_type10_valve config_data;
    };

    // Device_type20_drive.h // Device_type20_drive.cpp
    class Device_type20_drive : public Device_type_base {
        //..
        void insert_config_data(const Device_config_data_type20_drive& data);
    private:
        Device_config_data_type20_drive config_data;
    };

    //.... more derived classes like this

    // Devices.h // Devices.cpp
    class Devices {
    public:
        // Reads in existing Devices from a source

        // providing a function to add the data collection to existing Devices

        // writing out the configured devices back to the source
        void add_data(const Devices_config_data_collection& collection);
    private:
        struct data {
            std::vector<Device_type10_valve> type10;
            std::vector<Device_type20_drive> type20;
            //... and more
        };
    };

    // a lot more parts of the programm with similiar complexity but they are not devices
}

I usually put each class in its own h-file and cpp-file with the name like the class name.
The problem I have with this is that the class and file names get very long which doesn't make the code very nice to read.

So I was thinking of adding sub namespaces. One for all Devices to isolate this part from the rest of the program. There are more parts which have nothing to do with devices. And another one for the data classes. Then I could shorten the class names.

Which looks like this in pseudo C++ code:

namespace Application_name {    // for all the methods in the program

    namespace Device {


        namespace Config_data {

            //Device_config_data_type_base.h   // Device_config_data_type_base.cpp
            class Type_base {
                //..
            };

            //Device_config_data_type10_valve.h   // Device_config_data_type10_valve.cpp
            class Type10_valve : public Type_base {
                //...
            };

            //Device_config_data_type20_drive.h   // Device_config_data_type20_drive_valve.cpp

            class Type20_drive : public Type_base {
                //...
            };

            //.... more derived classes like this from Device_config_data_base

        }

        // Devices_data_collection.h    // Devices_data_collection.cpp
        class Collection {
        public:
            // put in raw data and make a collection of config data
        private:
            struct data {
                std::vector<Config_data::Type10_valve> type10;
                std::vector<Config_data::Type20_drive> type20;
                //... and more
            };

            // more stuff also sorts in valid and invalid inputs
        };

        // Devices_type_base.h // Devices_type_base.cpp
        class Type_base {
            //..
        };

        // Device_type10_valve.h // Device_type10_valve.cpp
        class Type10_valve : public Type_base {
            //..

            void insert_config_data(const Config_data::Type10_valve& data);
        private:
            // part of the private data is the config data

            Config_data::Type10_valve config_data;
        };

        // Device_type20_drive.h // Device_type20_drive.cpp
        class Type20_drive : public Type_base {
            //..
            void insert_config_data(const Config_data::Type20_drive& data);
        private:
            Config_data::Type20_drive config_data;
        };

        //.... more derived classes like this


    }

    // Devices.h // Devices.cpp
    class Devices {
    public:
        // Reads in existing Devices from a source
        // providing a function to add the data collection to existing Devices
        // writing out the configured devices back to the source
        void add_data(const Devices_config_data_collection& collection);
    private:
        struct data {
            std::vector<Device_type10_valve> type10;
            std::vector<Device_type20_drive> type20;
            //... and more
        };
    };

    // a lot more parts of the program with similar complexity but they are not devices
}

Note now I have a class Devices::type10_valve and Devices::Config_data::type10_valve.

I wonder if this is a good practice? Also now comes the problem with the cpp and h files. How to handle them now to represent the class names? If I shorten them I have a name clash with Devices::type10_valve and `Devices::Config_data::type10_valve because they are both type10_valve.cpp/h.

Is it a good practice to put them into subfolders on the hard disk and also refer the folders in the includes?

Please let me know what you think, what are your experiences.

Note I'm using Visual Studio 2017. Any suggestions for organizing issues like this specifically for it are also welcome

If you need more details let me know.

Best Answer

Namespaces are a very good way to organize your (larger code bases) code.

And like you indicated, having the filesystem file structure mimic the namespaces in some systematic way, is also a very good practice.

The key to doing this well lies in coming up with the right organizational decomposition, which of course is intimately tied to your particular application.

But what you offered as an example looked right on to me.

An example of a large class library using this sort of organizaitonal scheme would be Stroika (shared stream class)

One other slight hint, I wouldn't use a name like 'global_namespace'. I would there - use the name of your application, or project which all the source code is for (note - C++ has something called the global namespace and it means something different than yours).

Related Topic