DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Building Scalable Real-Time Apps with AstraDB and Vaadin
Register Now

Trending

  • Using OpenAI Embeddings Search With SingleStoreDB
  • Database Integration Tests With Spring Boot and Testcontainers
  • Health Check Response Format for HTTP APIs
  • How Web3 Is Driving Social and Financial Empowerment

Trending

  • Using OpenAI Embeddings Search With SingleStoreDB
  • Database Integration Tests With Spring Boot and Testcontainers
  • Health Check Response Format for HTTP APIs
  • How Web3 Is Driving Social and Financial Empowerment
  1. DZone
  2. Coding
  3. Java
  4. 6 Efficient Things You Can Do to Refactor a C++ Project

6 Efficient Things You Can Do to Refactor a C++ Project

Bartłomiej Filipek user avatar by
Bartłomiej Filipek
·
Sep. 03, 20 · Opinion
Like (2)
Save
Tweet
Share
11.38K Views

Join the DZone community and get the full member experience.

Join For Free

I took my old pet project from 2006, experimented, refactored it, and made it more "modern C++". Here are my lessons and six practical steps that you can apply to your projects.

Let's start.

Background and Test Project

All changes that I describe here are based on my experience with a pet project which I dig out from the studies. It's an application that visualises sorting algorithms. I wrote it in 2005/2006 and used C++98/03, Win32Api and OpenGL, all created in Visual Studio (probably 2003 if I remember :).

Here's the app preview:

Above you can see a cool animation of quick sort algorithm. The algorithm works on an array of values (can be randomised, sorted, reverse sorted, etc.) and performs a single step of the algorithm around 30 times per second. The input data is then taken and drawn as a diagram with some reflection underneath. The green element is the currently accessed value, and the light-blue section represents the part of the array that the algorithm is working on.

While the app looks nice, It has some awful ideas in the code... so why not improve it and experiment.

Here's the GitHub repo: github/fenbf/ViAlg-Update

Let's start with the first step:

Staying with GCC 3.0 is not helpful when GCC 10 is ready :)

Working in Visual Studio 2008 is not the best idea when VS 2019 is out there and stable :)

If you can, and your company policy allows that, and there are resources, then upgrade the compiler to the latest version you can get. Not only will you have a chance to leverage the latest C++ features, but also the compiler will have a lot of bugs fixed. Having regular updates can make your projects safer and more stable.

From my perspective, it's also good to update toolchains frequently. That way it's easier to fix broken code and have a smoother transition. If you update once per 5... 7 years then such a task seems to be "huge", and it's delayed and delayed.

Another topic is that when you have the compiler please remember about setting the correct C++ version!

You can use the latest VS 2019 and still compiler with C++11 flag, or C++14 (that might be beneficial, as the compiler bugs will be solved and you can enjoy the latest IDE functionalities). This will be also easier for you to upgrade to the C++17 standard once you have the process working.

You can, of course, go further than that and also update or get the best tools you can get for C++: most recent IDE, build systems, integrations, reviewing tools, etc, etc... but that's a story for a separate and long article :) I mentioned some techniques with tooling in my previous article: "Use the Force, Luke"... or Modern C++ Tools, so you may want to check it out as well.

2. Fix Code With Deprecated or Removed C++ Features

Once you have the compiler and the C++ version set you can fix some broken code or improve things that were deprecated in C++.

Here are some of the items that you might consider:

  • auto_ptr deprecated in C++11 and removed in C++17
  • functional stuff like bind1st, bind2nd, etc - use bind, bind_front or lambdas
  • dynamic exception specification, deprecated in C++11 and removed in C++17
  • the register keyword, removed in C++17
  • random_shuffle, deprecated since C++11 and removed in C++17
  • trigraphs removed in C++17
  • and many more

Your compiler can warn you about those features, and you can even use some extra tools like clang-tidy to modernise some code automatically. For example, try modernise_auto_ptr which can fix auto_ptr usage in your code. See more on my blog C++17 in details: fixes and deprecation - auto_ptr

And also here are the lists of removed/deprecated features between C++ versions:

3. Start Adding Unit Tests

That's a game-changer!

Not only unit tests allow me to be more confident about the code, but it also forces me to improve the code.

One handy part?

Making thing to compile without bringing all dependencies

For example I had the DataRendered class:

Java
 




x


 
1
class DataRenderer {
2
public:
3
    void Reset();
4
    void Render(const CViArray<float>& numbers, AVSystem* avSystem);
5
private:
6
    // ..
7
};


The renderer knows how to render array with numbers using the AVSystem. The problem is that AVSystem is a class which makes calls to OpenGL and it's not easy to test. To make the whole test usable, I decided to extract the interface from the AVSystem - it's called IRenderer. That way I can provide a test rendering system, and I can compile my test suite without any OpenGL function calls.

The new declaration of the DataRenderer::Render member function:

Java
 




xxxxxxxxxx
1


 
1
void Render(const CViArray<float>& numbers, IRenderer* renderer);


And a simple unit/component test:

Java
 




x
14


 
1
TEST(Decoupling, Rendering) {
2
    TestLogger testLogger;
3
    CAlgManager mgr(testLogger);
4
    TestRenderer testRenderer;
5
 
           
6
    constexpr size_t NumElements = 100;
7
 
           
8
    mgr.SetNumOfElements(NumElements);
9
    mgr.GenerateData(DataOrder::doSpecialRandomized);
10
    mgr.SetAlgorithm(ID_METHOD_QUICKSORT);
11
    mgr.Render(&testRenderer);
12
 
           
13
    EXPECT_EQ(testRenderer.numDrawCalls, NumElements);
14
}


With TestRenderer (it only has a counter for the draw calls) I can test if the whole thing is compiling and working as expected, without any burden from handling or mocking OpenGL. We'll continue with that topic later, see the 4th point.

If you use Visual Studio, you can use various testing frameworks, for example, here's some documentation:

4. Decouple or Extract Classes

While unit tests can expose some issues with coupling and interfaces, sometimes types simply look wrong. Have a look at the following class:

Java
 




xxxxxxxxxx
1
27


 
1
template <class T>
2
class CViArray {
3
public:
4
    CViArray(int iSize);
5
    CViArray(): m_iLast(-1), m_iLast2(-1), m_iL(-1), m_iR(-1) { }
6
    ~CViArray();
7
 
           
8
    void Render(CAVSystem *avSystem);
9
 
           
10
    void Generate(DataOrder dOrder);
11
    void Resize(int iSize);
12
    void SetSection(int iLeft, int iRight);
13
    void SetAdditionalMark(int iId);
14
    int GetSize()
15
 
           
16
    const T& operator [] (int iId) const;
17
    T& operator [] (int iId);
18
 
           
19
private:
20
    std::vector<T> m_vArray;
21
    std::vector<T> m_vCurrPos;  // for animation
22
    int m_iLast;            // last accessed element
23
    int m_iLast2;           // additional accesed element
24
    int m_iL, m_iR;         // highlighted section - left and right
25
 
           
26
    static constexpr float s_AnimBlendFactor = 0.1f;
27
};


As you can see ViArray tries to wrap a standard vector plus add some extra capabilities that can be used for Algorithm implementations.

But do we really have to have rendering code inside this class? That's not the best place.

We can extract the rendering part into a separate type (you've actually seen it in the 3rd point):

Java
 




x


 
1
class DataRenderer {
2
public:
3
    void Reset();
4
    void Render(const CViArray<float>& numbers, AVSystem* avSystem);
5
private:
6
    // ..
7
};


And now rather than calling:

Java
 




xxxxxxxxxx
1


 
1
array.Render(avSystem);


I have to write:

Java
 




xxxxxxxxxx
1


 
1
renderer.Render(array, avSystem);


Much better!

Here are some benefits of the new design:

  • It's extensible, easy to add new rendering features that won't spoil the array interface.
  • ViArray is focusing only on the things that are related to data/element processing.
  • You can use ViArray in situations when you don't need to render anything

We can also go further than that, see the next step:

5. Extract Non-member Functions

In the previous step you saw how I extracter Render method into a separate class... but there is still a suspicious code there:

Java
 




xxxxxxxxxx
1
10


 
1
template <class T>
2
class CViArray {
3
public:
4
    CViArray(int iSize);
5
    CViArray(): m_iLast(-1), m_iLast2(-1), m_iL(-1), m_iR(-1) { }
6
    ~CViArray();
7
 
           
8
    void Generate(DataOrder dOrder);
9
 
           
10
    // ...


Should the Generate function be inside this class?

It could be better if that's a non-member function, similar to algorithms that we have in the Standard Library.

Let's move the code out of that class:

Java
 




xxxxxxxxxx
1


 
1
template<typename T>
2
void GenerateData(std::vector<T>& outVec, DataOrder dOrder) {
3
    switch (dOrder) {
4
        // implement...
5
    }
6
}


It's still not the best approach; I could probably use iterators here so it can support various containers. But this can be the next step for refactoring and for now it's good enough.

All in all, after a few refactoring iterations, the ViArray class looks much better.

But it's not all, how about looking at global state?

6. Reduce the Global State

Loggers... they are handy but how to make them available for all compilation units and objects?

How about making them global?

Yes :)

While this was my first solution, back in 2006, in the newest version of the application, I refactored it, and now logger is just an object defined in main() and then passed to objects that need it.

Java
 




x


 
1
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
2
    CLog logger{ "log.html" };
3
 
           
4
    AppState appState{ logger };
5
 
           
6
    InitApp(logger, appState);
7
 
           
8
    // ...
9
}


And another topic: Do you see that AppState class? It's a class that wraps a two "managers" that were globals:

Before:

Java
 




x


 
1
CAlgManager g_algManager;
2
CAVSystem g_avSystem;


And after:

Java
 




x
13


 
1
struct AppState {
2
    explicit AppState(const CLog& logger);
3
 
           
4
    CAlgManager m_algManager;
5
    CAVSystem m_avSystem;
6
};
7
 
           
8
AppState::AppState(const CLog& logger) :
9
    m_algManager { logger},
10
    m_avSystem { logger}
11
{
12
    // init code...
13
}


And an object of the AppState type is defined inside main().

What are the benefits?

  • better control over the lifetime of the objects
    • it's important when I want to log something in destruction, so I need to make sure loggers are destroyed last
  • extracted initialisation code from one large Init() function

I have still some other globals that I plan to convert, so it's work in progress.

Summary

In the article, you've seen several techniques you can use to make your code a bit better. We covered updating compilers and toolchains, decoupling code, using unit tests, handling global state.

I should probably mention another point: Having Fun :)

If you such refactoring on production then maybe it's good to keep balance, but if you have a please to refactor your pet project... then why not experiment. Try new features, patters. This can teach you a lot.

And by the way: For my Patrons I also prepared an extended version of this article, it includes two more bullet points (about Keeping It Simple and Tooling), have a look: at the Patreon Page and join :)

Back To you

The techniques that I presented in the article are not carved in stone and bulletproof... I wonder what your techniques with legacy code are? Please share your comments below the article.

unit test code style c++ IT Java (programming language)

Published at DZone with permission of Bartłomiej Filipek, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Trending

  • Using OpenAI Embeddings Search With SingleStoreDB
  • Database Integration Tests With Spring Boot and Testcontainers
  • Health Check Response Format for HTTP APIs
  • How Web3 Is Driving Social and Financial Empowerment

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: