Search code examples
c++constantsconst-correctness

Am I breaking const-correctness if I expose a const and non-const API?


I'm designing a class and had some concerns about const-correctness, but I don't think I have a complete understanding of what the notion truly means. If I store a raw pointer to a non-const type and want to provide const and non-const accessors to that pointer, will that break const-correctness?

class A
{
public:
    void SetCount(int* pCount)
    {
        m_pCount = pCount;
    }

    void GetCount(const int** ppCount) const
    {
        *ppCount = m_pCount;
    }

    void GetCount(int** ppCount) const
    {
        *ppCount = m_pCount;
    }

private:
    int* m_pCount = nullptr;
};

int main()
{
    int count = 10;

    A a;
    a.SetCount(&count);

    const int* pCountConst = nullptr;
    a.GetCount(&pCountConst);

    // Prints 10.
    std::cout << *pCountConst << std::endl;

    int* pCountNonConst = nullptr;
    a.GetCount(&pCountNonConst);
    (*pCountNonConst)++;

    // Prints 11.
    std::cout << *pCountConst << std::endl;
}

pCount's type is const int*, so we expect that the underlying int will never change once initialized. It seems to me that we're breaking const-correctness here, is that true?


Solution

  • It depends on your semantics, but I don't think I would use the functions you have here. Instead, here are the two options I would consider:

    If you want A to own this count variable, then you should probably preserve const-ness when accessing it. In this case, you might write your functions like this:

    void GetCount(const int** ppCount) const
    {
        *ppCount = m_pCount;
    }
    
    void GetCount(int** ppCount) // note - omitted const
    {
        *ppCount = m_pCount;
    }
    

    This means that if you have a non-const A you can get a non-const counter, but if you have a const A you can only get a const counter.


    You could also mean that A simply observes some count variable. In that context, a const observer means that you're not going to change which int you're pointing to, but that thing itself might change. In that case, you might write your accessor like this:

    void GetCount(int** ppCount) const
    {
        *ppCount = m_pCount;
    }
    

    In general I would say to prefer the first method and preserve const-ness, but the other way is certainly valid in certain circumstances.