C ++ API Design: Cleaning Up the Open Interface

In my library, I want to publish a pure public API that does not detract from implementation details. However, be that as it may, this data leaks even into the public sphere: some classes have valid public methods that are used by the rest of the library, but are not very useful for the API user and, t be part of this. A simplified example of open source:

class Cookie;

class CookieJar {
public:
    Cookie getCookie();
}

class CookieMonster {
public:
    void feed(CookieJar cookieJar) {
        while (isHungry()) {
            cookieJar.getCookie();
        }
    }

    bool isHungry();
}

The method getCookie()for is CookieJarnot useful to the library user, who, apparently, does not like cookies in any case. However, it is used CookieMonsterto serve itself when assigned to it.

, . Pimpl , , API. , . :

class Cookie;
class CookieJarImpl;

class CookieJar {
public:
    CookieJarImpl* getImplementation() {
        return pimpl.get();
    }
private:
    std::unique_ptr<CookieJarImpl> pimpl;
}

, , , . , , CookieJarImpl.

. , API. , . , , , Pimpl. API , .

, - , . , , , .

Python: , , , , API . , , , , , , .

- ++ , - ? - , ?

+4
5

: - , API , , , , .

CRTP, πάντα ῥεῖ , , , - . . , , - . , , () . :

, API :

class CookieJar {
public:
    static std::unique_ptr<CookieJar> Create(unsigned capacity);

    bool isEmpty();
    void fill();

    virtual ~CookieJar() = 0 {};
};

class CookieMonster {
public:
    void feed(CookieJar* cookieJar);
    bool isHungry();
};

void main() {
    std::unique_ptr<CookieJar> jar = CookieJar::Create(20);
    jar->fill();
    CookieMonster monster;
    monster.feed(jar.get());
}

- CookieJar factory .

:

struct Cookie {
    const bool isYummy = true;
};

class CookieJarImpl : public CookieJar {
public:
    CookieJarImpl(unsigned capacity) :
        capacity(capacity) {}

    bool isEmpty() {
        return count == 0;
    }

    void fill() {
        count = capacity;
    }

    Cookie getCookie() {
        if (!isEmpty()) {
            count--;
            return Cookie();
        } else {
            throw std::exception("Where did all the cookies go?");
        }
    }

private:
    const unsigned capacity;
    unsigned count = 0;
};

// CookieJar implementation - simple wrapper functions replacing dynamic dispatch
std::unique_ptr<CookieJar> CookieJar::Create(unsigned capacity) {
    return std::make_unique<CookieJarImpl>(capacity);
}

bool CookieJar::isEmpty() {
    return static_cast<CookieJarImpl*>(this)->isEmpty();
}

void CookieJar::fill() {
    static_cast<CookieJarImpl*>(this)->fill();
}

// CookieMonster implementation
void CookieMonster::feed(CookieJar* cookieJar) {
    while (isHungry()) {
        static_cast<CookieJarImpl*>(cookieJar)->getCookie();
    }
}

bool CookieMonster::isHungry() {
    return true;
}

. factory, , , . , , , - .

, , , , static_casts reinterpret_casts, , , , , , . .

+2

:

struct Cookie {};

struct CookieJarData {
    int count;
    int cost;
    bool whatever;
    Cookie cookie;
};

struct CookieJarInternal {
    CookieJarInternal(CookieJarData *d): data{d} {}
    Cookie getCookie() { return data->cookie; }
private:
    CookieJarData *data;
};

struct CookieJar {
    CookieJar(CookieJarData *d): data{d} {}
    int count() { return data->count; }
private:
    CookieJarData *data;
};

template<typename... T>
struct CookieJarTemplate: CookieJarData, T... {
    CookieJarTemplate(): CookieJarData{}, T(this)... {}
};

using CookieJarImpl = CookieJarTemplate<CookieJar, CookieJarInternal>;

class CookieMonster {
public:
    void feed(CookieJarInternal &cookieJar) {
        while (isHungry()) {
            cookieJar.getCookie();
        }
    }

    bool isHungry() {
        return false;
    }
};

void userMethod(CookieJar &cookieJar) {}

int main() {
    CookieJarImpl impl;
    CookieMonster monster;

    monster.feed(impl);
    userMethod(impl);
}

, , .
- , , , . , , , , , .

+1

. CookieJarPrivate, CookieJar . CookieJarPrivate , API. CookieJar CookieJarPrivate friend. , cookiejar.h cookiejarprivate.h, friend, , CookieJarPrivate.

class Cookie;

class CookieJarPrivate {
public:
    Cookie getCookie();
private:
    CookieJarPrivate(CookieJar& jar) : m_jar(jar) {}
    CookieJar& m_jar;
};

class CookieJar {
    friend class CookieJarPrivate;
public:
    CookieJarPrivate getPrivate() { return *this; }
private:
    Cookie getCookie();
};

class CookieMonster {
public:
    void feed(CookieJar cookieJar) {
        while (isHungry()) {
            cookieJar.getPrivate().getCookie();
        }
    }

    bool isHungry();
};

Cookie CookieJarPrivate::getCookie() {
    return m_jar.getCookie();
}

CookieJarPrivate getPrivate(), getCookie(). , m_jar.getCookie() CookieJarPrivate::getCookie(). , , , getCookie() , , , .


- , friend CookieMonster, , , .. CookieMonster. friend, .

template <class T> class Restrict {
    friend T;
private:
    Restrict() {}
};

class Cookie;
class CookieMonster;

class CookieJar {
public:
    Cookie getCookie(Restrict<CookieMonster>);
};

class CookieMonster {
public:
    void feed(CookieJar cookieJar) {
        while (isHungry()) {
            cookieJar.getCookie({});
        }
    }

    bool isHungry();
};

, friend, . - , , , CookieMonster.

class PrivateAPI;
class Cookie;

class CookieJar {
public:
    Cookie getCookie(PrivateAPI);
};

class CookieMonster {
public:
    void feed(CookieJar cookieJar);

    bool isHungry();
};

class PrivateAPI {};

void CookieMonster::feed(CookieJar cookieJar) {
    while (isHungry()) {
        cookieJar.getCookie({});
    }
}
+1

CookieJar, , . STL ++ , , - (, , ..) cookie , isHungry .

getCookie() , CookieMonster CookieJar, CookieMonster getCookie(), .

    #include<vector>
    using namespace std;
    class Cookie
    {
      private:
       string type;
       string chocolateFlavor;
    }

    class CookieJar {
    friend class CookieMonster;
    public:
        CookieJar(){ 
           //loads a cookie jar with 10 cookies
           for (int i = 0; i = 10; i++) { 
              Cookie cookie; 
              cookieContainer.push_back(cookie);
           }
         }

    private:
        vector<Cookie> cookieContainer;
        Cookie getCookie(){
          //returns a cookie to feed and deletes one in the container
          Cookie toFeed = cookieContainer[0];
          cookieContainer[0] = *cookieContainer.back();
          cookieContainer.pop_back();
          return toFeed;
        }
    }

    class CookieMonster {
    public:
        void feed(CookieJar cookieJar) {
            while (isHungry()) {
                cookieJar.getCookie();
            }
        }
    private:
        bool isHungry();
    }
0

, :

struct Cookie {};

struct CookieJarBase {
    Cookie getCookie() { return Cookie{}; }
};

struct CookieMonster;
struct CookieJar;

struct CookieJar: private CookieJarBase {
    void accept(CookieMonster &);
};

struct CookieMonster {
    void feed(CookieJarBase &);
    bool isHungry();
};

void CookieJar::accept(CookieMonster &m) {
    CookieJarBase &base = *this;
    m.feed(base);
}

void CookieMonster::feed(CookieJarBase &cj) {
    while (isHungry()) {
        cj.getCookie();
    }
}

bool CookieMonster::isHungry() { return false; }

int main() {
    CookieMonster monster;
    CookieJar cj;

    cj.accept(monster);

    // the following line doesn't compile
    // for CookieJarBase is not accesible
    // monster.feed(cj);
}

, , getCookie CookieMonster.
, feed, accept.

To solve your problem is a virtual template method, which is simply not possible.
Otherwise, you cannot avoid virtual methods or friend declarations unless you want to disclose unsuitable methods, as in the above example.

In any case, this at least helps to hide internal methods, such as getCookiewhich you do not want to provide.

0
source

Source: https://habr.com/ru/post/1650699/


All Articles