Refactor the C++ guidelines (#2178)
* Refactor the C++ guidelines * Cleanup and adding TODOs * Update the C++ sidebar links
This commit is contained in:
Родитель
0cfed04068
Коммит
5bbad16b8f
|
@ -14,3 +14,4 @@ _site/
|
|||
.idea
|
||||
.DS_Store
|
||||
vendor/
|
||||
/.vs
|
||||
|
|
|
@ -155,14 +155,19 @@ entries:
|
|||
url: /clang_documentation.html
|
||||
- title: C++ Guidelines
|
||||
folderitems:
|
||||
- title: Introduction
|
||||
- title: Design
|
||||
url: /cpp_introduction.html
|
||||
- title: API Design
|
||||
url: /cpp_design.html
|
||||
- title: Implementation
|
||||
url: /cpp_implementation.html
|
||||
- title: Documentation
|
||||
url: /cpp_documentation.html
|
||||
subfolders:
|
||||
- title: API Documentation
|
||||
subfolderitems:
|
||||
- title: azure-storage-blob
|
||||
external_url: https://azure.github.io/azure-sdk-for-cpp/storage.html#azure-storage-blob
|
||||
- title: azure-storage-file-datalake
|
||||
external_url: https://azure.github.io/azure-sdk-for-cpp/storage.html#azure-storage-file-datalake
|
||||
- title: azure-storage-file-share
|
||||
external_url: https://azure.github.io/azure-sdk-for-cpp/storage.html#azure-storage-file-shares
|
||||
- title: Go Guidelines
|
||||
folderitems:
|
||||
- title: Introduction
|
||||
|
|
|
@ -1,693 +0,0 @@
|
|||
---
|
||||
title: "C++ Guidelines: API Design"
|
||||
keywords: guidelines cpp
|
||||
permalink: cpp_design.html
|
||||
folder: cpp
|
||||
sidebar: general_sidebar
|
||||
---
|
||||
|
||||
{% include draft.html content="The C++ Language guidelines are in DRAFT status" %}
|
||||
|
||||
The API surface of your client library must have the most thought as it is the primary interaction that the consumer has with your service.
|
||||
|
||||
## Naming and Declarations
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-concise" %} use clear, concise, and meaningful names.
|
||||
|
||||
{% include requirement/SHOULDNOT id="cpp-design-naming-abbrev" %} use abbreviations unless necessary or when they are commonly used and understood. For example, `az` is allowed since it is commonly used to mean `Azure`, and `iot` is used since it is a commonly understood industry term. However, using `kv` for Key Vault would not be allowed since `kv` is not commonly used to refer to Key Vault.
|
||||
|
||||
### Definitions
|
||||
|
||||
- **PascalCase** identifiers start with an uppercase letter and then use additional capital letters to divide words. Acronyms and initialisms are capitalized if they are 2 letters or shorter; otherwise only the first letter is capitalized. For example, `PascalCase`, `HttpRequest`, `JsonParser`, or `IOContext`.
|
||||
|
||||
- **camelCase** identifiers start with a lowercase letter and then use additional capital letters to divide words. Acronyms and initialisms that start an identifier are all lowercase if they begin an identifier, otherwise they follow the same 2 letters rule as PascalCase. For example, `camelCase`, `httpRequest`, `processHttp`, `ioContext`, `startIO`.
|
||||
|
||||
- **ALL_CAPITAL_SNAKE_CASE** identifiers are composed of entirely capital letters and divide words with underscores.
|
||||
|
||||
### Namespaces
|
||||
|
||||
Grouping services within a cloud infrastructure is common since it aids discoverability and provides structure to the reference documentation.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-namespaces" %} name namespaces using **PascalCase**.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-namespaces-hierarchy" %} use a root namespace of the form `Azure::<Group>::<Service>`. All consumer-facing APIs that are commonly used should exist within this namespace. The namespace is comprised of three parts:
|
||||
|
||||
- `Azure` indicates a common prefix for all Azure services.
|
||||
- `<Group>` is the group for the service. See the list below.
|
||||
- `<Service>` is the shortened service name.
|
||||
|
||||
{% include requirement/MUST id="general-namespaces-shortened-name" %} pick a shortened service name that allows the consumer to tie the package to the service being used. As a default, use the compressed service name. The namespace does **NOT** change when the branding of the product changes, so avoid the use of marketing names that may change.
|
||||
|
||||
A compressed service name is the service name without spaces. It may further be shortened if the shortened version is well known in the community. For example, "Azure Media Analytics" would have a compressed service name of `MediaAnalytics`, whereas "Azure Service Bus" would become `ServiceBus`.
|
||||
|
||||
{% include requirement/MUST id="general-namespaces-approved-list" %} use the following list as the group of services:
|
||||
|
||||
{% include tables/data_namespaces_pascal_case.md %}
|
||||
|
||||
If the client library does not seem to fit into the group list, contact the [Architecture Board] to discuss the namespace requirements.
|
||||
|
||||
{% include requirement/MUST id="general-namespaces-mgmt" %} place the management (Azure Resource Manager) API in the `Management` group. Use the grouping `Azure::Management::<Group>::<Service>` for the namespace. Since more services require control plane APIs than data plane APIs, other namespaces may be used explicitly for control plane only. Data plane usage is by exception only. Additional namespaces that can be used for control plane SDKs include:
|
||||
|
||||
{% include tables/mgmt_namespaces_pascal_case.md %}
|
||||
|
||||
Many `management` APIs do not have a data plane because they deal with management of the Azure account. Place the management library in the `Azure::Management` namespace. For example, use `Azure::Management::CostAnalysis` instead of `Azure::Management::Management::CostAnalysis`.
|
||||
|
||||
{% include requirement/MUSTNOT id="general-namespaces-similar-names" %} choose similar names for clients that do different things.
|
||||
|
||||
{% include requirement/MUST id="general-namespaces-registration" %} register the chosen namespace with the [Architecture Board]. Open an issue to request the namespace. See [the registered namespace list](registered_namespaces.html) for a list of the currently registered namespaces.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-namespaces-details" %} place private implementation details in a `Details` namespace.
|
||||
|
||||
{% highlight cpp %}
|
||||
namespace Azure { namespace Group { namespace Service {
|
||||
namespace Details {
|
||||
// Part of the private API
|
||||
struct HashComputation {
|
||||
int InternalBookkeeping;
|
||||
};
|
||||
|
||||
const int g_privateConstant = 1729;
|
||||
} // namespace Details
|
||||
|
||||
// Part of the public API
|
||||
struct UploadBlobRequest {
|
||||
unsigned char* Data;
|
||||
size_t DataLength;
|
||||
};
|
||||
|
||||
// Bad - private API in public namespace.
|
||||
struct HashComputation {
|
||||
int InternalBookkeeping;
|
||||
};
|
||||
const int g_privateConstant = 1729;
|
||||
}}} // namespace Azure::Group::Service
|
||||
{% endhighlight %}
|
||||
|
||||
#### Example Namespaces
|
||||
|
||||
Here are some examples of namespaces that meet these guidelines:
|
||||
|
||||
- `Azure::Data::Cosmos`
|
||||
- `Azure::Identity::ActiveDirectory`
|
||||
- `Azure::Iot::DeviceProvisioning`
|
||||
- `Azure::Storage::Blobs`
|
||||
- `Azure::Messaging::NotificationHubs` (the client library for Notification Hubs)
|
||||
- `Azure::Management::Messaging::NotificationHubs` (the management library for Notification Hubs)
|
||||
|
||||
Here are some namespaces that do not meet the guidelines:
|
||||
|
||||
- `microsoft::azure::CosmosDB` (not in the `Azure` namespace and does not use grouping, uses lowercase letters)
|
||||
- `azure::mixed_reality::kinect` (the grouping is not in the approved list and uses snake_case)
|
||||
- `Azure::Iot::IotHub::DeviceProvisioning` (too many levels in the group)
|
||||
|
||||
### Class Types (including `union`s and `struct`s)
|
||||
|
||||
Throughout this section, *class types* includes types with *class-key* `struct` or *class-key* `union`, consistent with the [C++ Standard](http://eel.is/c++draft/class#pre-4).
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-classes" %} name class types with **PascalCase**.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-classes-public-protected-variables" %} name `public` and `protected` member variables with **PascalCase**.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-classes-public-variables" %} name `private` member variables with an `m_` prefix, followed by a **camelCase** name. For example, `m_timeoutMs`.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-classes-functions" %} name member functions with **PascalCase**, except where the C++ Standard forbids this. For example, `UploadBlob`, or `operator[]`.
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-naming-classes-no-struct-keyword" %} declare classes with only public members using *class-key* `struct`.
|
||||
{% highlight cpp %}
|
||||
// Good
|
||||
struct OnlyPublicMembers {
|
||||
int Member;
|
||||
};
|
||||
|
||||
// Bad
|
||||
class OnlyPublicMembers {
|
||||
public:
|
||||
int Member;
|
||||
};
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-naming-classes-typedefs" %} define class types without using `typedef`s. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
// Good: Uses C++ style class declaration:
|
||||
struct IotClient {
|
||||
char* ApiVersion;
|
||||
IotClientCredentials* Credentials;
|
||||
int RetryTimeout;
|
||||
};
|
||||
|
||||
// Bad: Uses C-style typedef:
|
||||
typedef struct IotClient {
|
||||
char* ApiVersion;
|
||||
IotClientCredentials* Credentials;
|
||||
int RetryTimeout;
|
||||
} AzIotClient;
|
||||
{% endhighlight %}
|
||||
|
||||
### Variables
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-variables-public-global" %} name namespace scope variables intended for user consumption with **PascalCase**.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-variables-constants" %} name namespace scope `const` or `constexpr` variables intended for user consumption with **PascalCase** and a `c_` prefix.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-variables-public-global" %} name namespace scope non-constant variables intended only for internal consumption with a `g_` prefix followed by **camelCase**. For example, `g_applicationContext`. Note that all such cases will be in a `Details` namespace or an unnamed namespace.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-variables-local" %} name local variables and parameters with **camelCase**.
|
||||
|
||||
{% highlight cpp %}
|
||||
// Examples of the above naming rules:
|
||||
|
||||
namespace Azure { namespace Group { namespace Service {
|
||||
int PublicNamespaceScopeVariable; // these should be used sparingly
|
||||
const int c_PublicNamespaceScopeConstant = 42;
|
||||
constexpr int c_OtherPublicNamespaceScopeConstant = 42;
|
||||
constexpr char * c_PublicNamespaceScopeConstantPointer = nullptr; // const pointer to modifiable
|
||||
|
||||
void Function(int parameterName) {
|
||||
int localName;
|
||||
}
|
||||
|
||||
namespace Details {
|
||||
extern int g_internalUseGlobal;
|
||||
} // namespace Details
|
||||
|
||||
}}} // namespace Azure::Group::Service
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-variables-typing-units" %} use types to enforce units where possible. For example, the C++ standard library provides `std::chrono` which makes time conversions automatic.
|
||||
{% highlight cpp %}
|
||||
// Bad
|
||||
uint32 Timeout;
|
||||
|
||||
// Good
|
||||
std::chrono::milliseconds Timeout;
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-naming-units" %} include units in names when a type based solution to enforce units is not present. If a variable represents weight, or some other unit, then include the unit in the name so developers can more easily spot problems. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
// Bad
|
||||
uint32 Timeout;
|
||||
uint32 MyWeight;
|
||||
|
||||
// Good
|
||||
std::chrono::milliseconds Timeout;
|
||||
uint32 MyWeightKg;
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-variables-one-per-line" %} Declare or define each variable on its own line, except when declaring bitfields. An exception can be made when declaring bitfields (to clarify that the variable is a part of one bitfield). The use of bitfields in general is discouraged.
|
||||
|
||||
### Enums and Enumerators
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-enum" %} name `enum class`es and enumerators using **PascalCase**.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-enum-class" %} use `enum class` for enumerations. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
enum class PinState {
|
||||
Off,
|
||||
On
|
||||
};
|
||||
{% endhighlight %}
|
||||
|
||||
### Functions
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-functions" %} name functions with **PascalCase**. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
namespace Azure { namespace Group { namespace Service {
|
||||
namespace Details {
|
||||
// Part of the private API
|
||||
[[nodiscard]] int64_t ComputeHash(int32_t a, int32_t b) noexcept;
|
||||
} // namespace Details
|
||||
|
||||
// Part of the public API
|
||||
[[nodiscard]] CatHerdClient CatHerdCreateClient(char* herdName);
|
||||
|
||||
// Bad - private API in public namespace.
|
||||
[[nodiscard]] int64_t ComputeHash(int32_t a, int32_t b) noexcept;
|
||||
}}} // namespace Azure::Group::Service
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-naming-functions-noexcept" %} declare all functions that can never throw exceptions `noexcept`.
|
||||
|
||||
### Templates
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-templates" %} name function templates and class templates the same as one would name non-templates.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-templates-parameters" %} name template arguments with **PascalCase**.
|
||||
|
||||
### Macros
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-naming-macros-avoid" %} avoid use of macros. It is acceptable to use macros in the following situations. Use outside of these situations should contact the Azure Review Board.
|
||||
|
||||
* Platform, compiler, or other environment detection (for example, `_WIN32` or `_MSC_VER`).
|
||||
* Emission or suppression of diagnostics.
|
||||
* Emission or supression of debugging asserts.
|
||||
* Import declarations. (`__declspec(dllimport)`, `__declspec(dllexport)`)
|
||||
|
||||
> TODO: Need to involve Charlie in how we want to talk about import declarations
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-macros-caps" %} name macros with **ALL_CAPITAL_SNAKE_CASE**.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-macros-form" %} prepend macro names with `AZ_<SERVICENAME>` to make macros unique.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-naming-macros-functions" %} use macros where an inline function or function template would achieve the same effect. Macros are not required for code efficiency.
|
||||
|
||||
{% highlight cpp %}
|
||||
// Bad
|
||||
##define MAX(a,b) ((a > b) ? a : b)
|
||||
|
||||
// Good
|
||||
template<class T>
|
||||
[[nodiscard]] inline T Max(T x, T y) {
|
||||
return x > y ? x : y;
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-naming-macros-donoevil" %} change syntax via macro substitution. It [makes the program unintelligible](https://gist.github.com/aras-p/6224951) to all but the perpetrator.
|
||||
|
||||
## Physical Design
|
||||
|
||||
> TODO: Move this to implementation or move the headers discussion from implementation here
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-physical-include-quotes" %} include files using quotes (") for files within the same git repository, and angle brackets (<>) for external dependencies.
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-physical-unnamed-namespace" %} declare all types that are only used within the same `.cpp` file in an unnamed namespace. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
namespace {
|
||||
struct HashComputation {
|
||||
int InternalBookkeeping;
|
||||
};
|
||||
} // unnamed namespace
|
||||
{% endhighlight %}
|
||||
|
||||
## Logical Design
|
||||
|
||||
### Type Safety Recommendations
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-rule-of-zero" %} In class types, implement the "rule of zero", the "rule of 3", or the "rule of 5". That is, of the special member functions, a type should implement exactly one of the following:
|
||||
|
||||
* No copy constructor, no copy assignment operator, no move constructor, no move assignment operator, or destructor.
|
||||
* A copy constructor, a copy assignment operator, no move constructor, no move assignment operator, and a destructor.
|
||||
* A copy constructor, a copy assignment operator, a move constructor, a move assignment operator, and a destructor.
|
||||
|
||||
This encourages use of resource managing types like std::unique_ptr (which implements the rule of 5) as a compositional tool in more complex data models that implement the rule of zero.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-initialize-all-data" %} provide types which are usable when default-initialized. (That is, every constructor must initialize all type invariants, not assume members have default values of 0 or similar.)
|
||||
|
||||
{% highlight cpp %}
|
||||
class TypeWithInvariants {
|
||||
int m_member;
|
||||
public:
|
||||
TypeWithInvariants() noexcept : member(0) {} // Good: initializes all parts of the object
|
||||
[[nodiscard]] int Next() noexcept {
|
||||
return m_member++;
|
||||
}
|
||||
};
|
||||
|
||||
class BadTypeWithInvariants {
|
||||
int m_member;
|
||||
public:
|
||||
BadTypeWithInvariants() {} // Bad: Does not initialize all parts of the object
|
||||
int Next() {
|
||||
return m_member++;
|
||||
}
|
||||
};
|
||||
|
||||
void TheCustomerCode() {
|
||||
TypeWithInvariants a{}; // value-initializes a TypeWithInvariants, OK
|
||||
TypeWithInvariants b; // default-initializes a TypeWithInvariants, we want this to be OK
|
||||
BadTypeWithInvariants c{}; // value-initializes a BadTypeWithInvariants, OK
|
||||
BadTypeWithInvariants d; // default-initializes a BadTypeWithInvariants, this will trigger
|
||||
// undefined behavior if anyone calls d.Next()
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-no-getters-or-setters" %} define getters and setters for data transfer objects. Expose the members directly to users unless you need to enforce some constraints on the data. For example:
|
||||
{% highlight cpp %}
|
||||
// Good - no restrictions on values
|
||||
struct ExampleRequest {
|
||||
int RetryTimeoutMs;
|
||||
const char* Text;
|
||||
};
|
||||
|
||||
// Bad - no restrictions on parameters and access is not idiomatic
|
||||
class ExampleRequest {
|
||||
int m_retryTimeoutMs;
|
||||
const char* m_text;
|
||||
public:
|
||||
[[nodiscard]] int GetRetryTimeoutMs() const noexcept {
|
||||
return m_retryTimeoutMs;
|
||||
}
|
||||
void SetRetryTimeoutMs(int i) noexcept {
|
||||
m_retryTimeoutMs = i;
|
||||
}
|
||||
[[nodiscard]] const char* GetText() const noexcept {
|
||||
return m_text;
|
||||
}
|
||||
void SetText(const char* i) noexcept {
|
||||
m_text = i;
|
||||
}
|
||||
};
|
||||
|
||||
// Good - type maintains invariants
|
||||
class TypeWhichEnforcesDataRequirements {
|
||||
size_t m_size;
|
||||
int* m_data;
|
||||
public:
|
||||
[[nodiscard]] size_t GetSize() const noexcept {
|
||||
return m_size;
|
||||
}
|
||||
void AddData(int i) noexcept {
|
||||
m_data\[m_size++\] = i;
|
||||
}
|
||||
};
|
||||
|
||||
// Also Good
|
||||
class TypeWhichClamps {
|
||||
int m_retryTimeout;
|
||||
public:
|
||||
[[nodiscard]] int GetRetryTimeout() const noexcept {
|
||||
return m_retryTimeout;
|
||||
}
|
||||
void SetRetryTimeout(int i) noexcept {
|
||||
if (i < 0) i = 0; // clamp i to the range [0, 1000]
|
||||
if (i > 1000) i = 1000;
|
||||
m_retryTimeout = i;
|
||||
}
|
||||
};
|
||||
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-optimize-position" %} declare variables in structures organized by use in a manner that minimizes memory wastage because of compiler alignment issues and size. All things being equal, use alphabetical ordering.
|
||||
|
||||
{% highlight cpp %}
|
||||
// Bad
|
||||
struct Foo {
|
||||
int A; // the compiler will insert 4 bytes of padding after A to align B
|
||||
char *B;
|
||||
int C;
|
||||
char *D;
|
||||
};
|
||||
|
||||
// Good
|
||||
struct Foo {
|
||||
int A; // C is now stored in that padding
|
||||
int C;
|
||||
char *B;
|
||||
char *D;
|
||||
};
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-enumsareinternal" %} use enums to model any data sent to the service. Use enums only for types completely internal to the client library. For example, an enum to disable Nagle's algorithm would be OK, but an enum to ask the service to create a particular entity kind is not.
|
||||
|
||||
### Const and Reference members
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-no-const-or-reference-members" %} declare types with const or reference members. Const and reference members artificially make your types non-Regular as they aren't assignable, and have surprising interactions with C++ Core language rules. For example, many accesses to const or reference members would need to involve use of `std::launder` to avoid undefined behavior, but `std::launder` was added in C++17, a later version than the SDKs currently target. See C++ Core Working Group [CWG1116 "Aliasing of union members"](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1116), [CWG1776 "Replacement of class objects containing reference members"](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1776), and [P0137R1 "Replacement of class objects containing reference members"](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0137r1.html) for additional details.
|
||||
|
||||
If you want a type to provide effectively const data except assignment, declare all your member functions const. Const member functions only get a const view of the class' data.
|
||||
|
||||
{% highlight cpp %}
|
||||
// Bad
|
||||
class RetrySettings {
|
||||
const int m_maxRetryCount;
|
||||
public:
|
||||
int GetMaxRetryCount() {
|
||||
// intent: disallow m_maxRetryCount = aDifferentValue;
|
||||
return m_maxRetryCount;
|
||||
}
|
||||
};
|
||||
|
||||
// Good
|
||||
class RetrySettings {
|
||||
int m_maxRetryCount;
|
||||
public:
|
||||
int GetMaxRetryCount() const {
|
||||
// still prohibits m_maxRetryCount = aDifferentValue; without making RetrySettings un-assignable
|
||||
return m_maxRetryCount;
|
||||
}
|
||||
};
|
||||
{% endhighlight %}
|
||||
|
||||
### Integer sizes
|
||||
|
||||
The following integer rules are listed in rough priority order. Integer size selections are primarily driven by service future compatibility. For example, just because today a service might have a 2 GiB file size limit does not mean that it will have such a limit forever. We believe 64 bit length limits will be sufficient for sizes an individual program works with for the foreseeable future.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-integer-files" %} Represent file sizes with `int64_t`, even on 32 bit platforms.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-integer-memory-buffers" %} Represent memory buffer sizes with `size_t` or `ptrdiff_t` as appropriate for the environment. Between the two, choose the type likely to need the fewest conversions in application. For example, we would prefer signed `ptrdiff_t` in most cases because signed integers behave like programmers expect more consistently, but the SDK will need to transact with `malloc`, `std::vector`, and/or `std::string` which all speak unsigned `size_t`.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-integer-service-values" %} Represent any other integral quantity passed over the wire to a service using `int64_t`, even if the service uses a 32 bit constant internally today.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-integer-not-int" %} Use `int` under any circumstances, including `for` loop indexes. Those should usually use `ptrdiff_t` or `size_t` under the buffer size rule above.
|
||||
|
||||
{% include requirement/MAY id="cpp-design-logical-integer-otherwise" %} Use any integer type in the `intN_t` or `uintN_t` family as appropriate for quantities not enumerated above, such as calculated values internal to the SDK such as retry counts.
|
||||
|
||||
### Secure functions
|
||||
|
||||
{% include requirement/SHOULDNOT id="cpp-design-logical-no-ms-secure-functions" %} use [Microsoft security enhanced versions of CRT functions](https://docs.microsoft.com/cpp/c-runtime-library/security-enhanced-versions-of-crt-functions) to implement APIs that need to be portable across many platforms. Such code is not portable and is not compatible with either the C or C++ Standards. See [arguments against]( http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm).
|
||||
|
||||
> TODO: Verify with the security team, and what are the alternatives?
|
||||
|
||||
### Client interface
|
||||
|
||||
> TODO: This section needs to be driven by code in the Core library.
|
||||
|
||||
#### Network requests
|
||||
|
||||
> TODO: This section needs to be driven by code in the Core library.
|
||||
|
||||
#### Authentication
|
||||
|
||||
Azure services use a variety of different authentication schemes to allow clients to access the service. Conceptually, there are two entities responsible in this process: a credential and an authentication policy. Credentials provide confidential authentication data. Authentication policies use the data provided by a credential to authenticate requests to the service.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-support-all-auth-techniques" %} support all authentication techniques that the service supports and are available to a client application (as opposed to service side). C is used only for client applications when talking to Azure, so some authentication techniques may not be valid.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-use-azure-core" %} use credential and authentication policy implementations from the Azure Core library where available.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-prefer-token-auth" %} provide credential types that can be used to fetch all data needed to authenticate a request to the service in a non-blocking atomic manner for each authentication scheme that does not have an implementation in Azure Core.
|
||||
|
||||
{% include requirement/MUST id="cpp-apisurface-auth-in-constructors" %} provide service client constructors or factories that accept any supported authentication credentials.
|
||||
|
||||
{% include requirement/SHOULDNOT id="cpp-design-logical-client-surface-no-connection-strings" %} support providing credential data via a connection string. Connection string interfaces should be provided __ONLY IF__ the service provides a connection string to users via the portal or other tooling.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-client-surface-no-connection-string-ctors" %} support constructing a service client with a connection string unless such connection string. Provide a `CreateFromConnectionString` static member function which returns a client instead to encourage customers to choose non-connection-string-based authentication.
|
||||
|
||||
#### Response formats
|
||||
|
||||
Requests to the service fall into two basic groups - methods that make a single logical request, or a deterministic sequence of requests. An example of a *single logical request* is a request that may be retried inside the operation. An example of a *deterministic sequence of requests* is a paged operation.
|
||||
|
||||
The *logical entity* is a protocol neutral representation of a response. For HTTP, the logical entity may combine data from headers, body and the status line. A common example is exposing an ETag header as a property on the logical entity in addition to any deserialized content from the body.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-return-logical-entities" %} optimize for returning the logical entity for a given request. The logical entity MUST represent the information needed in the 99%+ case.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-expose-raw" %} *make it possible* for a developer to get access to the complete response, including the status line, headers and body. The client library MUST follow the language specific guidance for accomplishing this.
|
||||
|
||||
For example, you may choose to do something similar to the following:
|
||||
|
||||
{% highlight cpp %}
|
||||
namespace Azure { namespace Group { namespace Service {
|
||||
struct JsonShortItem {
|
||||
// JSON decoded structure.
|
||||
};
|
||||
|
||||
struct JsonShortPagedResults {
|
||||
uint32 Size;
|
||||
JsonShortItem* Items;
|
||||
};
|
||||
|
||||
struct JsonShortRawPagedResults {
|
||||
HTTP_HEADERS* Headers;
|
||||
uint16 StatusCode;
|
||||
byte* RawBody;
|
||||
JsonShortPagedResults* Results;
|
||||
};
|
||||
|
||||
class ShortItemsClient {
|
||||
JsonShortPagedResults* JsonGetShortListItems() const;
|
||||
JsonShortRawPagedResults* JsonGetShortListItemsWithResponse(client, /* extra params */);
|
||||
};
|
||||
}}} // namespace Azure::Group::Service
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-document-raw-stream" %} document and provide examples on how to access the raw and streamed response for a given request, where exposed by the client library. We do not expect all methods to expose a streamed response.
|
||||
|
||||
For methods that combine multiple requests into a single call:
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-client-no-headers-if-confusing" %} return headers and other per-request metadata unless it is obvious as to which specific HTTP request the methods return value corresponds to.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-expose-data-for-composite-failures" %} provide enough information in failure cases for an application to take appropriate corrective action.
|
||||
|
||||
#### Pagination
|
||||
|
||||
Although object-orientated languages can eschew low-level pagination APIs in favor of high-level abstractions, C acts as a lower level language and thus embraces pagination APIs provided by the service. You should work within the confines of the paging system provided by the service.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-pagination-use-paging" %} export the same paging API as the service provides.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-pagination-cpp-last-page" %} indicate in the return type if the consumer has reached the end of the result set.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-pagination-size-of-page" %} indicate in the return type how many items were returned by the service, and have a list of those items for the consumer to iterate over.
|
||||
|
||||
#### Enumerations
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-enumerations-no-enums" %} use `enum` or `enum class` for values shared "over the wire" with a service, to support future compatibility with the service where additional values are added. Such values should be persisted as strings in client data structures instead.
|
||||
|
||||
{% include requirement/MAY id="cpp-design-logical-client-enumerations-enumish-pattern" %} provide an 'extensible enum' pattern for storing service enumerations which provides reasonable constant values. This pattern stores the value as a string but provides public static member fields with the individual values for customer consumption. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
#include <azure/core/strings.hpp> // for Azure::Core::Strings::LocaleInvariantCaseInsensitiveEqual
|
||||
#include <utility> // for std::move
|
||||
namespace Azure { namespace Group { namespace Service {
|
||||
|
||||
// an "Extensible Enum" type
|
||||
class KeyType {
|
||||
std::string m_value;
|
||||
public:
|
||||
// Provide `explicit` conversion from string or types convertible to string:
|
||||
explicit KeyType(const std::string& value) : m_value(value) { }
|
||||
explicit KeyType(std::string&& value) : m_value(std::move(value)) { }
|
||||
explicit KeyType(const char* value) : m_value(value) { }
|
||||
|
||||
// Provide an equality comparison. If the service treats the enumeration case insensitively,
|
||||
// use LocaleInvariantCaseInsensitiveEqual to prevent differing locale settings from affecting
|
||||
// the SDK's behavior:
|
||||
bool operator==(const KeyType& other) const noexcept {
|
||||
return Azure::Core::Strings::LocaleInvariantCaseInsensitiveEqual(m_value, other.m_value);
|
||||
}
|
||||
|
||||
bool operator!=(const KeyType& other) const noexcept { return !(*this == other); }
|
||||
|
||||
// Provide a "Get" accessor
|
||||
const std::string& Get() const noexcept { return m_value; }
|
||||
|
||||
// Provide your example values as static const members
|
||||
const static KeyType Ec;
|
||||
const static KeyType EcHsm;
|
||||
const static KeyType Rsa;
|
||||
const static KeyType RsaHsm;
|
||||
const static KeyType Oct;
|
||||
};
|
||||
}}} // namespace Azure::Group::Service
|
||||
|
||||
|
||||
// in a .cpp file:
|
||||
namespace Azure { namespace Group { namespace Service {
|
||||
const KeyType KeyType::Ec{"EC"};
|
||||
const KeyType KeyType::EcHsm{"EC-HSM"};
|
||||
const KeyType KeyType::Rsa{"RSA"};
|
||||
const KeyType KeyType::RsaHsm{"RSA-HSM"};
|
||||
const KeyType KeyType::Oct{"OCT"};
|
||||
}}} // namespace Azure::Group::Service
|
||||
{% endhighlight %}
|
||||
|
||||
### Error handling
|
||||
|
||||
Error handling is an important aspect of implementing a client library. It is the primary method by which problems are communicated to the consumer. Because we intend for the C client libraries to be used on a wide range of devices with a wide range of reliability requirements, it's important to provide robust error handling.
|
||||
|
||||
We distinguish between several different types of errors:
|
||||
|
||||
* Exhaustion / Act of God
|
||||
: errors like running out of stack space, or dealing with power failure that, in general, can not be anticipated and after which it may be hard to execute any more code, let alone recover. Code handling these errors needs to be written to *very* specific requirements, for example not doing any allocations and never growing the stack.
|
||||
* Pre-Conditions
|
||||
: Pre-Condition errors occur when a caller violates the expectations of a function, for example by passing an out-of-range value or a null pointer. These are always avoidable by the direct caller, and will always require a source code change (by the caller) to fix.
|
||||
* Post-Conditions
|
||||
: Post-Condition violations happen when some function didn't do the correct thing, these are _always_ bugs in the function itself, and users shouldn't be expected to handle them.
|
||||
* Heap Exhaustion (Out of Memory)
|
||||
: Running out of memory.
|
||||
* Recoverable Error
|
||||
: Things like trying to open a file that doesn't exist, or trying to write to a full disk. These kinds of errors can usually be handled by a function's caller directly, and need to be considered by callers that want to be robust.
|
||||
|
||||
#### Exhaustion / Act of God
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-errorhandling-actofgod-no-return" %} return an error to the caller.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-errorhandling-actofgod-crash" %} crash, if possible. This means calling some form of fast failing function, like `abort`.
|
||||
|
||||
Note: if your client library needs to be resilient to these kinds of errors you must either provide a fallback system, or construct your code in a way to facilitate proving that such errors can not occur.
|
||||
|
||||
#### Pre-conditions
|
||||
{% include requirement/MAY id="cpp-design-logical-errorhandling-prec-check" %} check preconditions on function entry.
|
||||
|
||||
{% include requirement/MAY id="cpp-design-logical-errorhandling-prec-disablecheck" %} privide a means to disable precondition checks in release / optimized builds.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-errorhandling-prec-crash" %} crash, if possible. This means calling some form of fast failing function, like `abort`.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-errorhandling-prec-exceptions" %} throw a C++ exception.
|
||||
|
||||
#### Post Conditions
|
||||
|
||||
{% include requirement/SHOULDNOT id="cpp-design-logical-errorhandling-postc-check" %} check post-conditions in a way that changes the computational complexity of the function.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-errorhandling-postc-disablecheck" %} provide a way to disable postcondition checks, and omit checking code from built binaries.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-errorhandling-postc-crash" %} crash, if possible. This means calling some form of fast failing function, like `abort`.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-errorhandling-postc-exceptions" %} throw a C++ exception.
|
||||
|
||||
#### Heap Exhaustion (Out of Memory)
|
||||
|
||||
{% include requirement/MAY id="cpp-design-logical-errorhandling-oom-crash" %} crash. For example, this may mean dereferencing a nullptr returned by malloc, or explicitly checking and calling abort.
|
||||
|
||||
Note that on some comonly deployed platforms like Linux, handling heap exhaustion from user mode is not possible in a default configuration.
|
||||
|
||||
{% include requirement/MAY id="cpp-design-logical-errorhandling-oom-bad-alloc" %} propagate a C++ exception of type `std::bad_alloc` when encountering an out of memory condition. We do not expect the program to continue in a recoverable state after this occurs. Note that most standard library facilities and the built in `operator new` do this automatically, and we want to allow use of other facilities that may throw here.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-errorhandling-oom-throw" %} throw bad_alloc from the SDK code itself.
|
||||
|
||||
#### Recoverable errors
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-errorhandling-recov-reporting" %} report errors by throwing C++ exceptions defined in the Azure C++ Core Library.
|
||||
|
||||
> TODO: The Core library needs to provide exceptions for the common failure modes, e.g. the same values as `az_result` in the C SDK.
|
||||
|
||||
For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
class Herd {
|
||||
bool m_hasShyCats;
|
||||
int m_numCats;
|
||||
public:
|
||||
void CountCats(int* cats) {
|
||||
if(m_hasShyCats) {
|
||||
throw std::runtime_error("shy cats are not allowed");
|
||||
}
|
||||
*cats = m_numCats;
|
||||
}
|
||||
};
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-errorhandling-recov-error" %} produce a recoverable error when any HTTP request fails with an HTTP status code that is not defined by the service/Swagger as a successful status code.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-errorhandling-recov-document" %} document all exceptions each function and its transitive dependencies may throw, except for `std::bad_alloc`.
|
||||
|
||||
#### C++ Exceptions
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-errorhandling-exceptions-other" %} `throw` exceptions, except those from the Azure C++ Core library as described in the error handling section.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-errorhandling-exceptions" %} propagate exceptions thrown by user code, callbacks, or dependencies. Assume any user-provided callback will propagate C++ exceptions unless the SDK documents that the callback must be completely non-throwing.
|
||||
|
||||
{% highlight cpp %}
|
||||
template<class Callback>
|
||||
void ApiFunc(const Callback& c) {
|
||||
// best
|
||||
c();
|
||||
|
||||
// allowed, but results in a less pretty debugging experience for customers
|
||||
try {
|
||||
c();
|
||||
} catch (...) {
|
||||
// clean up
|
||||
throw; // throw; rethrows the original exception object
|
||||
}
|
||||
|
||||
// prohibited
|
||||
try {
|
||||
c();
|
||||
} catch (...) {
|
||||
// missing throw;
|
||||
}
|
||||
}
|
||||
{% endhighlight %}
|
|
@ -1,212 +0,0 @@
|
|||
---
|
||||
title: "C++ Guidelines: Documentation"
|
||||
keywords: guidelines cpp
|
||||
permalink: cpp_documentation.html
|
||||
folder: cpp
|
||||
sidebar: general_sidebar
|
||||
---
|
||||
|
||||
{% include draft.html content="The C++ Language guidelines are in DRAFT status" %}
|
||||
|
||||
There are several documentation deliverables that must be included in or as a companion to your client library. Beyond complete and helpful API documentation within the code itself (docstrings), you need a great README and other supporting documentation.
|
||||
|
||||
* `README.md` - Resides in the root of your library's directory within the SDK repository; includes package installation and client library usage information. ([example][README-EXAMPLE])
|
||||
* `API reference` - Generated from the docstrings in your code; published on docs.microsoft.com.
|
||||
* `Code snippets` - Short code examples that demonstrate single (atomic) operations for the champion scenarios you've identified for your library; included in your README, docstrings, and Quickstart.
|
||||
* `Quickstart` - Article on docs.microsoft.com that is similar to but expands on the README content; typically written by your service's content developer.
|
||||
* `Conceptual` - Long-form documentation like Quickstarts, Tutorials, How-to guides, and other content on docs.microsoft.com; typically written by your service's content developer.
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-contentdev" %} include your service's content developer in the Architecture Board review for your library. To find the content developer you should work with, check with your team's Program Manager.
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-contributors-guide" %} follow the [Azure SDK Contributors Guide]. (MICROSOFT INTERNAL)
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-style-guide" %} adhere to the specifications set forth in the Microsoft style guides when you write public-facing documentation. This applies to both long-form documentation like a README and the docstrings in your code. (MICROSOFT INTERNAL)
|
||||
|
||||
* [Microsoft Writing Style Guide].
|
||||
* [Microsoft Cloud Style Guide].
|
||||
|
||||
{% include requirement/SHOULD id="cpp-docs-to-silence" %} attempt to document your library into silence. Preempt developers' usage questions and minimize GitHub issues by clearly explaining your API in the docstrings. Include information on service limits and errors they might hit, and how to avoid and recover from those errors.
|
||||
|
||||
As you write your code, *doc it so you never hear about it again.* The less questions you have to answer about your client library, the more time you have to build new features for your service.
|
||||
|
||||
### Docstrings
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-doxygen" %} include docstrings compatible with the [doxygen](http://www.doxygen.nl/index.html) tool for generating reference documentation.
|
||||
|
||||
For example, a (very) simple docstring might look like:
|
||||
{% highlight cpp %}
|
||||
/**
|
||||
* @class client
|
||||
* @brief The client represents the resources required for a connection to an Azure AppcConfiguration resource.
|
||||
*/
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-doxygen-cmd" %} use doxygen's `@cmd` style docstrings
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-doxygen-params" %} format parameter documentation like the following:
|
||||
|
||||
{% highlight cpp %}
|
||||
/* <....documentation....>
|
||||
* @param[<direction>] <param_name> description
|
||||
* <....documentation....>
|
||||
*/
|
||||
{% endhighlight %}
|
||||
|
||||
For example:
|
||||
{% highlight cpp %}
|
||||
namespace azure::storage::blob {
|
||||
/**
|
||||
* @brief execute a blocking get request
|
||||
*
|
||||
* @param[in] client
|
||||
* @param[in] path_and_query The query to execute relative to the base url of the client
|
||||
* @param[out] result
|
||||
* @param[out] result_sz size of the result
|
||||
*/
|
||||
void req_get(client* client, const char* path_and_query, unsigned char** result, size_t* result_sz);
|
||||
} // namespace azure::storage::blob
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-doxygen-nullable" %} document what happens to parameters that are set to null.
|
||||
|
||||
For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
namespace azure::animals::cats {
|
||||
/**
|
||||
* @brief get properties of a cat (e.g. hair color, weight, floof)
|
||||
*
|
||||
* @param[in] our_cat the cat to operate on
|
||||
* @param[out] props pointer to an array of num_props, or null
|
||||
* @param[in,out] num_props pointer to the number of properties to retrieve or to a location to store the number of
|
||||
* properties queried as described below
|
||||
*
|
||||
* If @p props is NULL then return the number of properties available in @p num_props,
|
||||
* otherwise return @p num_props into the array at @p props
|
||||
*/
|
||||
void get_cat_properties(cat* our_cat, cat_properties* props, size_t* num_props);
|
||||
} // namespace azure::animals::cats
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-doxygen-failure" %} document which exceptions your function can propagate.
|
||||
|
||||
### Code snippets
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-include-snippets" %} include example code snippets alongside your library's code within the repository. The snippets should clearly and succinctly demonstrate the operations most developers need to perform with your library. Include snippets for every common operation, and especially for those that are complex or might otherwise be difficult for new users of your library. At a bare minimum, include snippets for the champion scenarios you've identified for the library.
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-build-snippets" %} build and test your example code snippets using the repository's continuous integration (CI) to ensure they remain functional.
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-snippets-in-docstrings" %} include the example code snippets in your library's docstrings so they appear in its API reference. If the language and its tools support it, ingest these snippets directly into the API reference from within the docstrings.
|
||||
|
||||
For example, consider a function called `do_something_or_other`:
|
||||
{% highlight cpp %}
|
||||
namespace azure::sdks::example {
|
||||
/**
|
||||
* @brief some class type
|
||||
*/
|
||||
struct some_class {
|
||||
int member;
|
||||
|
||||
/**
|
||||
* @brief do something, or maybe do some other thing
|
||||
*/
|
||||
void do_something_or_other();
|
||||
};
|
||||
} // azure::sdks::example
|
||||
{% endhighlight %}
|
||||
It can be used as follows:
|
||||
{% highlight cpp %}
|
||||
/**
|
||||
* @example example_1.cpp
|
||||
*/
|
||||
int main() {
|
||||
using azure::sdks::example::some_class;
|
||||
some_class a;
|
||||
a.do_something_or_other();
|
||||
return 1;
|
||||
}
|
||||
{% endhighlight %}
|
||||
When doxygen processes these files, it will see the `@example` command in example_1.cpp and
|
||||
add it to the "examples" section of the documentation, it will also see the usage of
|
||||
`some_struct` in the example and add a link from the documentation of `some_struct` to the
|
||||
documentation (including source code) for `example_1.cpp`.
|
||||
|
||||
Use `@include` or `@snippet` to include examples directly in the documentation for a function or structure.
|
||||
For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
//
|
||||
// @brief some structure type
|
||||
//
|
||||
struct some_struct {
|
||||
int member;
|
||||
};
|
||||
|
||||
//
|
||||
// @brief do something, or maybe do some other thing
|
||||
// see @snippet example_1.cpp use some_struct
|
||||
//
|
||||
void do_something_or_other(some_struct* s);
|
||||
{% endhighlight %}
|
||||
It can be used as follows:
|
||||
{% highlight cpp %}
|
||||
/**
|
||||
* @example example_1.cpp
|
||||
*/
|
||||
int main() {
|
||||
/** [use some_struct] */
|
||||
some_struct a;
|
||||
do_something_or_other(&a);
|
||||
/** [use some_struct] */
|
||||
return 1;
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
Note that automatic links from documentation to examples will only be generated in struct documentation,
|
||||
not in function documentation. To generate a link from a function's documentation to an example use `@dontinclude`. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
/**
|
||||
* @brief do something, or maybe do some other thing
|
||||
* @dontinclude example_1.cpp
|
||||
*/
|
||||
void do_something_or_other(const some_struct& s);
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-docs-operation-combinations" %} combine more than one operation in a code snippet unless it's required for demonstrating the type or member, or it's *in addition to* existing snippets that demonstrate atomic operations. For example, a Cosmos DB code snippet should not include both account and container creation operations--create two different snippets, one for account creation, and one for container creation.
|
||||
|
||||
Combined operations cause unnecessary friction for a library consumer by requiring knowledge of additional operations which might be outside their current focus. It requires them to first understand the tangential code surrounding the operation they're working on, then carefully extract just the code they need for their task. The developer can no longer simply copy and paste the code snippet into their project.
|
||||
|
||||
### Buildsystem integration
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-buildsystem" %} Provide a buildsystem target called "docs" to build
|
||||
the documentation
|
||||
|
||||
{% include requirement/MUST id="cpp-docs-buildsystem-option" %} Provide an option `BUILD_DOCS` to control
|
||||
building of the docs, this should default to `OFF`
|
||||
|
||||
To provide this you can use the CMake `FindDoxygen` module as follows:
|
||||
|
||||
{% highlight cmake %}
|
||||
option(BUILD_DOCS "Build documentation" OFF)
|
||||
if(BUILD_DOCS)
|
||||
find_package(Doxygen REQUIRED doxygen)
|
||||
|
||||
# note: DOXYGEN_ options are strings to cmake, not
|
||||
# booleans, thus use only YES and NO
|
||||
set(DOXYGEN_GENERATE_XML YES)
|
||||
set(DOXYGEN_GENERATE_HTML YES)
|
||||
|
||||
set(DOXYGEN_EXTRACT_PACKAGE YES)
|
||||
set(DOXYGEN_INLINE_SIMPLE_STRUCTS YES)
|
||||
set(DOXYGEN_TYPEDEF_HIDES_STRUCT YES)
|
||||
doxygen_add_docs(docs
|
||||
${PROJECT_SOURCE_DIR}/inc
|
||||
${PROJECT_SOURCE_DIR}/doc
|
||||
${PROJECT_SOURCE_DIR}/src
|
||||
COMMENT "Generate Documentation")
|
||||
endif()
|
||||
{% endhighlight %}
|
||||
|
||||
|
||||
{% include refs.md %}
|
|
@ -8,114 +8,119 @@ sidebar: general_sidebar
|
|||
|
||||
{% include draft.html content="The C++ Language guidelines are in DRAFT status" %}
|
||||
|
||||
## Supported platforms
|
||||
> TODO: This section needs to be driven by code in the Core library.
|
||||
|
||||
{% include requirement/MUST id="cpp-platform-min" %} support the following platforms and associated compilers when implementing your client library.
|
||||
## API Implementation
|
||||
|
||||
### Windows
|
||||
This section describes guidelines for implementing Azure SDK client libraries. Please note that some of these guidelines are automatically enforced by code generation tools.
|
||||
|
||||
| Operating System | Version | Architectures | Compiler Version | Notes
|
||||
|----------------------|---------------|---------------|-----------------------------------------|------
|
||||
| Windows Client | 7 SP1+, 8.1 | x64, x86 | MSVC 14.16.x, MSVC 14.20x |
|
||||
| Windows 10 Client | Version 1607+ | x64, x86, ARM | MSVC 14.16.x, MSVC 14.20x |
|
||||
| Windows 10 Client | Version 1909+ | ARM64 | MSVC 14.20x |
|
||||
| Nano Server | Version 1803+ | x64, ARM32 | MSVC 14.16.x, MSVC 14.20x |
|
||||
| Windows Server | 2012 R2+ | x64, x86 | MSVC 14.16.x, MSVC 14.20x |
|
||||
### Service Client
|
||||
|
||||
### Mac
|
||||
> TODO: add a brief mention of the approach to implementing service clients.
|
||||
|
||||
| Operating System | Version | Architectures | Compiler Version | Notes
|
||||
|---------------------------------|---------------|---------------|-----------------------------------------|------
|
||||
| macOS | 10.13+ | x64 | XCode 9.4.1 |
|
||||
#### Service Methods
|
||||
|
||||
### Linux
|
||||
> TODO: Briefly introduce that service methods are implemented via an `HttpPipeline` instance. Mention that much of this is done for you using code generation.
|
||||
|
||||
| Operating System | Version | Architectures | Compiler Version | Notes
|
||||
|---------------------------------|---------------|---------------|-----------------------------------------|------
|
||||
| Red Hat Enterprise Linux <br> CentOS <br> Oracle Linux | 7+ | x64 | gcc-4.8 | [Red Hat lifecycle](https://access.redhat.com/support/policy/updates/errata/) <br> [CentOS lifecycle](https://wiki.centos.org/FAQ/General#head-fe8a0be91ee3e7dea812e8694491e1dde5b75e6d) <br> [Oracle Linux lifecycle](http://www.oracle.com/us/support/library/elsp-lifetime-069338.pdf)
|
||||
| Debian | 9+ | x64 | gcc-6.3 | [Debian lifecycle](https://wiki.debian.org/DebianReleases)
|
||||
| Ubuntu | 18.04, 16.04 | x64 | gcc-7.3 | [Ubuntu lifecycle](https://wiki.ubuntu.com/Releases)
|
||||
| Linux Mint | 18+ | x64 | gcc-7.3 | [Linux Mint lifecycle](https://www.linuxmint.com/download_all.php)
|
||||
| openSUSE | 15+ | x64 | gcc-7.5 | [OpenSUSE lifecycle](https://en.opensuse.org/Lifetime)
|
||||
| SUSE Enterprise Linux (SLES) | 12 SP2+ | x64 | gcc-4.8 | [SUSE lifecycle](https://www.suse.com/lifecycle/)
|
||||
##### HttpPipeline
|
||||
|
||||
{% include requirement/SHOULD id="cpp-platform" %} support the following additional platforms and associated compilers when implementing your client library.
|
||||
The following example shows a typical way of using `HttpPipeline` to implement a service call method. The `HttpPipeline` will handle common HTTP requirements such as the user agent, logging, distributed tracing, retries, and proxy configuration.
|
||||
|
||||
> TODO: Show an example of invoking the pipeline
|
||||
|
||||
{% include requirement/SHOULDNOT id="cpp-cpp-extensions" %} use compiler extensions. Examples of extensions to avoid include:
|
||||
##### HttpPipelinePolicy/Custom Policies
|
||||
|
||||
* [MSVC compiler extensions](https://docs.microsoft.com/cpp/build/reference/microsoft-extensions-to-c-and-cpp)
|
||||
* [clang language extensions](https://clang.llvm.org/docs/LanguageExtensions.html)
|
||||
* [GNU C compiler extensions](https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html)
|
||||
The HTTP pipeline includes a number of policies that all requests pass through. Examples of policies include setting required headers, authentication, generating a request ID, and implementing proxy authentication. `HttpPipelinePolicy` is the base type of all policies (plugins) of the `HttpPipeline`. This section describes guidelines for designing custom policies.
|
||||
|
||||
Use the appropriate options for each compiler to prevent the use of such extensions.
|
||||
> TODO: Show how to customize a pipeline
|
||||
|
||||
{% include requirement/MUST id="cpp-cpp-options" %} use compiler flags to identify warnings:
|
||||
#### Service Method Parameters
|
||||
|
||||
| Compiler | Compiler Flags |
|
||||
|:-------------------------|------------------|
|
||||
| gcc | `-Wall -Wextra` |
|
||||
| cpp and XCode | `-Wall -Wextra` |
|
||||
| MSVC | `/W4` |
|
||||
> TODO: This section needs to be driven by code in the Core library.
|
||||
|
||||
When configuring your client library, particular care must be taken to ensure that the consumer of your client library can properly configure the connectivity to your Azure service both globally (along with other client libraries the consumer is using) and specifically with your client library.
|
||||
##### Parameter Validation
|
||||
|
||||
### Client configuration
|
||||
In addition to [general parameter validation guidelines](introduction.md#cpp-parameters):
|
||||
|
||||
{% include requirement/MUST id="cpp-config-global-config" %} use relevant global configuration settings either by default or when explicitly requested to by the user, for example by passing in a configuration object to a client constructor.
|
||||
> TODO: Briefly show common patterns for parameter validation
|
||||
|
||||
{% include requirement/MUST id="cpp-config-for-different-clients" %} allow different clients of the same type to use different configurations.
|
||||
### Supporting Types
|
||||
|
||||
{% include requirement/MUST id="cpp-config-optout" %} allow consumers of your service clients to opt out of all global configuration settings at once.
|
||||
> TODO: This section needs to be driven by code in the Core library.
|
||||
|
||||
{% include requirement/MUST id="cpp-config-global-overrides" %} allow all global configuration settings to be overridden by client-provided options. The names of these options should align with any user-facing global configuration keys.
|
||||
#### Serialization {#cpp-usage-json}
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-config-defaults-nochange" %} Change the default values of client
|
||||
configuration options based on system or program state.
|
||||
> TODO: This section needs to be driven by code in the Core library.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-config-defaults-nobuildchange" %} Change default values of
|
||||
client configuration options based on how the client library was built.
|
||||
##### JSON Serialization
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-config-behaviour-changes" %} change behavior based on configuration changes that occur after the client is constructed. Hierarchies of clients inherit parent client configuration unless explicitly changed or overridden. Exceptions to this requirement are as follows:
|
||||
> TODO: This section needs to be driven by code in the Core library.
|
||||
|
||||
1. Log level, which must take effect immediately across the Azure SDK.
|
||||
2. Tracing on/off, which must take effect immediately across the Azure SDK.
|
||||
#### Enumeration-like Structs
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-config-noruntime" %} use client library specific runtime
|
||||
configuration such as environment variables or a config file. Keep in mind that many IOT devices
|
||||
won't have a filesystem or an "environment block" to read from.
|
||||
As described in [general enumeration guidelines](introduction.md#cpp-enums), you should use `enum` types whenever passing or deserializing a well-known set of values to or from the service.
|
||||
There may be times, however, where a `struct` is necessary to capture an extensible value from the service even if well-known values are defined,
|
||||
or to pass back to the service those same or other user-supplied values:
|
||||
|
||||
## Parameter validation
|
||||
- The value is retrieved and deserialized from service, and may contain a value not supported by the client library.
|
||||
- The value is roundtripped: the value is retrieved and deserialized from the service, and may later be serialized and sent back to the server.
|
||||
|
||||
The service client will have several methods that perform requests on the service. _Service parameters_ are directly passed across the wire to an Azure service. _Client parameters_ are not passed directly to the service, but used within the client library to fulfill the request. Examples of client parameters include values that are used to construct a URI, or a file that needs to be uploaded to storage.
|
||||
> TODO: Content in this section may need a new home.
|
||||
|
||||
{% include requirement/MUST id="cpp-params-client-validation" %} validate client parameters.
|
||||
{% include requirement/MUST id="cpp-design-naming-enum" %} name `enum class`es and enumerators using **PascalCase**.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-params-server-validation" %} validate service parameters. This includes null checks, empty strings, and other common validating conditions. Let the service validate any request parameters.
|
||||
{% include requirement/MUST id="cpp-design-naming-enum-class" %} use `enum class` for enumerations. For example:
|
||||
|
||||
{% include requirement/MUST id="cpp-params-check-devex" %} validate the developer experience when the service parameters are invalid to ensure appropriate error messages are generated by the service. If the developer experience is compromised due to service-side error messages, work with the service team to correct prior to release.
|
||||
{% highlight cpp %}
|
||||
enum class PinState {
|
||||
Off,
|
||||
On
|
||||
};
|
||||
{% endhighlight %}
|
||||
|
||||
## Network requests
|
||||
#### Using Azure Core Types
|
||||
|
||||
> TODO: Implement the spirit of the general guidelines for network requests - explicitly, how to use Azure Core for making network requests
|
||||
##### Implementing Subtypes of Operation\<T\> {#cpp-implement-operation}
|
||||
|
||||
## Authentication
|
||||
Subtypes of `Operation<T>` are returned from service client methods invoking long running operations.
|
||||
|
||||
When implementing authentication, don't open up the consumer to security holes like PII (personally identifiable information) leakage or credential leakage. Credentials are generally issued with a time limit, and must be refreshed periodically to ensure that the service connection continues to function as expected. Ensure your client library follows all current security recommendations and consider an independent security review of the client library to ensure you're not introducing potential security problems for the consumer.
|
||||
{% include requirement/MUST id="cpp-lro-return" %} check the value of `IsDone` in subclass implementations of `PollInternal` and `PollUntilDoneInternal` and immediately return the result of `GetRawResponse` if it is true.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-implementing-no-persistence-auth" %}
|
||||
persist, cache, or reuse security credentials. Security credentials should be considered short lived to cover both security concerns and credential refresh situations.
|
||||
> TODO: Show an example implementation for Operation<T>.
|
||||
|
||||
If your service implements a non-standard credential system (one that is not supported by Azure Core), then you need to produce an authentication policy for the HTTP pipeline that can authenticate requests given the alternative credential types provided by the client library.
|
||||
{% include requirement/MUST id="cpp-lro-return" %} throw from methods on `Operation<T>` subclasses in the following scenarios.
|
||||
|
||||
{% include requirement/MUST id="cpp-implementing-secure-auth-erase" %} Use a "secure" function to zero authentication or authorization credentials as soon as possible once they are no longer needed. Examples of such functions
|
||||
include: `SecureZeroMemory`, `memset_s`, and `explicit_bzero`. Examples of insecure functions include `memset`. An optimizer may notice that the credentials are
|
||||
never accessed again, and optimize away the call to `memset`, resulting in the credentials remaining in memory.
|
||||
- If an underlying service operation call from `Poll` or `PollUntilDone` throws, re-throw `RequestFailedException` or its subtype.
|
||||
- If the operation completes with a non-success result, throw `RequestFailedException` or its subtype from `Poll` or `PollUntilDone`.
|
||||
- Include any relevant error state information in the exception message.
|
||||
|
||||
{% include requirement/MUST id="cpp-implementing-auth-policy" %}
|
||||
provide a suitable authentication policy that authenticates the HTTP request in the HTTP pipeline when using non-standard credentials. This includes custom connection strings, if supported.
|
||||
> TODO: Show an example of how to handle errors.
|
||||
|
||||
## Logging
|
||||
- If the ```Value``` property is evaluated after the operation failed (```HasValue``` is false and ```IsDone``` is true) throw the same exception as the one thrown when the operation failed.
|
||||
|
||||
> TODO: Show an example of how to throw in this case.
|
||||
|
||||
- If the ```Value``` property is evaluated before the operation is complete (```IsDone``` is false) throw ```TODO: What to throw```.
|
||||
- The exception message should be: "The operation has not yet completed."
|
||||
|
||||
> TODO: DO we want this behavior.
|
||||
> TODO: Show an example of how to throw in this case.
|
||||
|
||||
### SDK Feature Implementation
|
||||
|
||||
#### Configuration
|
||||
|
||||
> TODO: This section needs to be driven by code in the Core library.
|
||||
|
||||
#### Logging
|
||||
|
||||
Request logging will be done automatically by the `HttpPipeline`. If a client library needs to add custom logging, follow the [same guidelines](implementation.md#general-logging) and mechanisms as the pipeline logging mechanism. If a client library wants to do custom logging, the designer of the library must ensure that the logging mechanism is pluggable in the same way as the `HttpPipeline` logging policy.
|
||||
|
||||
{% include requirement/MUST id="dotnet-logging-follow-guidelines" %} follow [the logging section of the Azure SDK General Guidelines](implementation.md#general-logging) if logging directly (as opposed to through the `HttpPipeline`).
|
||||
|
||||
##### C++ Logging specific details
|
||||
|
||||
> TODO: This additional logging info may need a new home.
|
||||
|
||||
Client libraries must support robust logging mechanisms so that the consumer can adequately diagnose issues with the method calls and quickly determine whether the issue is in the consumer code, client library code, or service.
|
||||
|
||||
|
@ -165,28 +170,25 @@ In general, our advice to consumers of these libraries is to establish logging i
|
|||
|
||||
{% include requirement/MUST id="cpp-logging-exceptions" %} log exceptions thrown as a `Warning` level message. If the log level set to `Verbose`, append stack trace information to the message.
|
||||
|
||||
## Dependencies
|
||||
#### Distributed Tracing {#cpp-distributedtracing}
|
||||
|
||||
Dependencies bring in many considerations that are often easily avoided by avoiding the
|
||||
dependency.
|
||||
{% include draft.html content="Guidance coming soon ..." %}
|
||||
|
||||
- **Versioning** - Many programming languages do not allow a consumer to load multiple versions of the same package. So, if we have an client library that requires v3 of package Foo and the consumer wants to use v5 of package Foo, then the consumer cannot build their application. This means that client libraries should not have dependencies by default.
|
||||
- **Size** - Consumer applications must be able to deploy as fast as possible into the cloud and move in various ways across networks. Removing additional code (like dependencies) improves deployment performance.
|
||||
- **Licensing** - You must be conscious of the licensing restrictions of a dependency and often provide proper attribution and notices when using them.
|
||||
- **Compatibility** - Often times you do not control a dependency and it may choose to evolve in a direction that is incompatible with your original use.
|
||||
- **Security** - If a security vulnerability is discovered in a dependency, it may be difficult or time consuming to get the vulnerability corrected if Microsoft does not control the dependencies code base.
|
||||
> TODO: Add guidance for distributed tracing implementation
|
||||
|
||||
{% include requirement/MUST id="cpp-dependencies-stdlibcpp" %} use the [C++ standard library](https://en.cppreference.com/w/).
|
||||
#### Telemetry
|
||||
|
||||
{% include requirement/MUST id="cpp-dependencies-azure-core" %} depend on the Azure Core library for functionality that is common across all client libraries. This library includes APIs for HTTP connectivity, global configuration, and credential handling.
|
||||
> TODO: Add guidance regarding user agent strings
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-dependencies-approved-only" %} be dependent on any other packages within the client library distribution package. Dependencies are by-exception and need a thorough vetting through architecture review. This does not apply to build dependencies, which are acceptable and commonly used.
|
||||
### Testing
|
||||
|
||||
{% include requirement/SHOULD id="cpp-dependencies-vendoring" %} consider copying or linking required code into the client library in order to avoid taking a dependency on another package that could conflict with the ecosystem. Make sure that you are not violating any licensing agreements and consider the maintenance that will be required of the duplicated code. ["A little copying is better than a little dependency"][1] (YouTube).
|
||||
We believe testing is a part of the development process, so we expect unit and integration tests to be a part of the source code. All components must be covered by automated testing, and developers should strive to test corner cases and main flows for every use case.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-dependencies-concrete" %} depend on concrete logging, dependency injection, or configuration technologies (except as implemented in the Azure Core library). The client library will be used in applications that might be using the logging, DI, and configuration technologies of their choice.
|
||||
All code should contain, at least, requirements, unit tests, end-to-end tests, and samples.
|
||||
|
||||
### C and C++ language specifics
|
||||
Tests should be written using the [Google Test][] library.
|
||||
|
||||
### Language-specific other
|
||||
|
||||
Unlike many other programming languages, which have large runtimes, the C++ standard runtime is limited in functionality and scope. The standard library covers areas such as memory and string manipulation, standard input/output, floating point and others. However, many of the features required for modern applications and services; e.g. those required for networking and advanced memory management are not part of the standard library. Instead, many of those features are included in open source C++ libraries that are also cross-platform with good support for Windows, OSX and most Linux platforms. Because of that support and because Azure SDK implementations will need such functionality, it is expected that client libraries will take dependencies on these libraries. Ensure the version matches to allow for compatibility when an application integrates multiple client libraries.
|
||||
|
||||
|
@ -196,43 +198,469 @@ Unlike many other programming languages, which have large runtimes, the C++ stan
|
|||
|
||||
{% include requirement/MUST id="cpp-dependencies-adparch" %} consult the [Architecture Board] if you wish to use a dependency that is not on the list of approved dependencies.
|
||||
|
||||
## Testing
|
||||
|
||||
We believe testing is a part of the development process, so we expect unit and integration tests to be a part of the source code. All components must be covered by automated testing, and developers should strive to test corner cases and main flows for every use case.
|
||||
|
||||
All code should contain, at least, requirements, unit tests, end-to-end tests, and samples.
|
||||
|
||||
Tests should be written using the [Google Test][] library.
|
||||
|
||||
## Coding style
|
||||
|
||||
{% include requirement/MUST id="cpp-style-clang-format" %} format your source code with `clang-format`, using the configuration file located in [the azure-sdk-for-cpp repo](https://github.com/Azure/azure-sdk-for-cpp/blob/master/.clang-format).
|
||||
|
||||
### Files
|
||||
|
||||
{% include requirement/MUST id="cpp-style-filenaming" %} name all files as lowercase, in a directory of the service short name. Separate words with underscores, and end with the appropriate extension (`.cpp` or `.hpp`). For example, `iot_credential.cpp` is valid, while `IoTCredential.cl` is not.
|
||||
|
||||
{% include requirement/MUST id="cpp-style-privateapi-hdr" %} place an include file that is not part of the public API in an `internal` directory. Do not include the service short name. For example, `<azure/internal/credential.hpp>`.
|
||||
|
||||
{% include requirement/MUST id="cpp-style-filenames" %} use characters in the range `[a-z0-9_]` for the name portion (before the file extension). No other characters are permitted.
|
||||
|
||||
Filenames should be concise, but convey what role the file plays within the library.
|
||||
|
||||
{% include requirement/MUST id="cpp-style-headerguards" %} use `#pragma once`
|
||||
{% include requirement/MUSTNOT id="cpp-test-implicit-assign" %} use implicit assignment inside a test. This is generally an accidental omission of the second `=` of the logical compare. The following is confusing and prone to error.
|
||||
|
||||
{% highlight cpp %}
|
||||
#pragma once
|
||||
|
||||
// Contents of a given header
|
||||
if (a = b) { ... }
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MAY id="cpp-style-whole-sdk-header" %} have a header file that includes an entire client library. For example, `<azure/speech.hpp>`.
|
||||
Does the programmer really mean assignment here? Sometimes yes, but usually no. Instead use explicit tests and avoid assignment with an implicit test. The recommended form is to do the assignment before doing the test:
|
||||
|
||||
{% include requirement/SHOULD id="cpp-style-sub-sdk-header" %} have headers for smaller components that make sense to be used together. For example, `<azure/speech/translation.hpp>`.
|
||||
{% highlight cpp %}
|
||||
a = b;
|
||||
if (a) { ... }
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-style-change-headers" %} substantially change the names exposed by the header in response to macros or other controls. For example, `NOMINMAX` or `WIN32_LEAN_AND_MEAN` from `<Windows.h>`.
|
||||
{% include requirement/MUSTNOT id="cpp-no-register" %} use the register keyword. Modern compilers will put variables in registers automatically.
|
||||
|
||||
## Tooling
|
||||
{% include requirement/MUST id="cpp-be-const-correct" %} be `const` correct. C++ provides the `const` keyword to allow passing as parameters objects that cannot change to indicate when a method doesn't modify its object. Using `const` in all the right places is called "const correctness."
|
||||
|
||||
{% include requirement/MUST id="cpp-use-hashif" %} use `#if` instead of `#ifdef`. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
// Bad example
|
||||
#ifdef DEBUG
|
||||
TemporaryDebuggerBreak();
|
||||
#endif
|
||||
{% endhighlight %}
|
||||
|
||||
Someone else might compile the code with turned-of debug info like:
|
||||
|
||||
{% highlight cpp %}
|
||||
cc -c lurker.cc -DDEBUG=0
|
||||
{% endhighlight %}
|
||||
|
||||
Alway use `#if` if you have to use the preprocessor. This works fine, and does the right thing, even if `DEBUG` is not defined at all (!)
|
||||
|
||||
{% highlight cpp %}
|
||||
// Good example
|
||||
#if DEBUG
|
||||
TemporaryDebuggerBreak();
|
||||
#endif
|
||||
{% endhighlight %}
|
||||
|
||||
If you really need to test whether a symbol is defined or not, test it with the `defined()` construct, which allows you to add more things later to the conditional without editing text that's already in the program:
|
||||
|
||||
{% highlight cpp %}
|
||||
#if !defined(USER_NAME)
|
||||
#define USER_NAME "john smith"
|
||||
#endif
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-large-comments" %}
|
||||
Use `#if` to comment out large code blocks.
|
||||
|
||||
Sometimes large blocks of code need to be commented out for testing. The easiest way to do this is with an `#if 0` block:
|
||||
|
||||
{% highlight cpp %}
|
||||
void Example()
|
||||
{
|
||||
great looking code
|
||||
|
||||
#if 0
|
||||
many lines of code
|
||||
#endif
|
||||
|
||||
more code
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
You can't use `/**/` style comments because comments can't contain comments and a large block of your code will probably contain connects.
|
||||
|
||||
Do not use `#if 0` directly. Instead, use descriptive macro names:
|
||||
|
||||
{% highlight cpp %}
|
||||
#if NOT_YET_IMPLEMENTED
|
||||
#if OBSOLETE
|
||||
#if TEMP_DISABLED
|
||||
{% endhighlight %}
|
||||
|
||||
Always add a short comment explaining why it is commented out.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-" %} put data definitions in header files. For example, this should be avoided:
|
||||
|
||||
{% highlight cpp %}
|
||||
/* aheader.hpp */
|
||||
int x = 0;
|
||||
{% endhighlight %}
|
||||
|
||||
It's bad magic to have space consuming code silently inserted through the innocent use of header files. It's not common practice to define variables in the header file, so it will not occur to developers to look for this when there are problems. Instead, define the variable once in a source file and then use an `extern` statement to reference it in the header file.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-no-magic-numbers" %} use magic numbers. A magic number is a bare naked number used in source code. It's magic because no-one will know what it means after a while. This significantly reduces maintainability. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
// Don't write this.
|
||||
if (19 == foo) { RefundLotsMoney(); }}
|
||||
else { HappyDaysIKnowWhyIAmHere(); }
|
||||
{% endhighlight %}
|
||||
|
||||
Instead of magic numbers, use a real name that means something. You can use `constexpr` for names. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
constexpr int WE_GOOFED = 19;
|
||||
|
||||
if (WE_GOOFED == foo) { RefundLotsMoney(); }
|
||||
else { HappyDaysIKnowWhyIAmHere(); }
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-check-syscall-errors" %} check every system call for an error return, unless you know you wish to ignore errors. For example, `printf` returns an error code but it is rarely relevant. Cast the return to (void) if you do not care about the error code.
|
||||
|
||||
{% highlight cpp %}
|
||||
(void)printf("The return value is ignored");
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-include-errorstr" %} include the system error text when reporting system error messages.
|
||||
|
||||
#### Complexity Management
|
||||
|
||||
{% include requirement/SHOULD id="cpp-init-all-vars" %} Initialize all variables. Only leave them
|
||||
uninitialized if there is a real performance reason to do so. Use static and dynamic analysis tools to
|
||||
check for uninitialized access. You may leave "result" variables uninitialized so long as they clearly do
|
||||
not escape from the innermost lexical scope.
|
||||
|
||||
{% include requirement/SHOULD id="cpp-function-size" %} limit function bodies to one page of code (40 lines, approximately).
|
||||
|
||||
{% include requirement/MUST id="cpp-document-null-bodies" %} document null statements. Always document a null body for a `for` or `while` statement so that it is clear the null body is intentional.
|
||||
|
||||
{% include requirement/MUST id="cpp-use-explicit-compares" %} use explicit comparisons when testing for failure. Use `if (FAIL != f())` rather than `if (f())`, even though FAIL may have the value 0 which C considers to be false. An explicit test will help you out later when somebody decides that a failure return should be -1 instead of 0.
|
||||
|
||||
Explicit comparison should be used even if the comparison value will never change. e.g. `if (!(bufsize % sizeof(int)))` should be written as `if (0 == (bufsize % sizeof(int))` to reflect the numeric (not boolean) nature of the test.
|
||||
|
||||
A frequent trouble spot is using `strcmp` to test for string equality. You should **never** use a default action. The preferred approach is to use an inline function:
|
||||
|
||||
{% highlight cpp %}
|
||||
inline bool StringEqual(char *a, char *b) {
|
||||
return (0 == strcmp(a, b));
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
~ Should
|
||||
{% include requirement/SHOULDNOT id="cpp-embedded-assign" %} use embedded assignments. There is a time and a place for embedded assignment statements. In some constructs, there is no better way to accomplish the results without making the code bulkier and less readable.
|
||||
|
||||
{% highlight cpp %}
|
||||
while (EOF != (c = getchar())) {
|
||||
/* process the character */
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
However, one should consider the tradeoff between increased speed and decreased maintainability that results when embedded assignments are used in artificial places.
|
||||
|
||||
#### Templates
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-templates" %} name function templates and class templates the same as one would name non-templates.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-templates-parameters" %} name template arguments with **PascalCase**.
|
||||
|
||||
#### Macros
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-naming-macros-avoid" %} avoid use of macros. It is acceptable to use macros in the following situations. Use outside of these situations should contact the Azure Review Board.
|
||||
|
||||
* Platform, compiler, or other environment detection (for example, `_WIN32` or `_MSC_VER`).
|
||||
* Emission or suppression of diagnostics.
|
||||
* Emission or supression of debugging asserts.
|
||||
* Import declarations. (`__declspec(dllimport)`, `__declspec(dllexport)`)
|
||||
|
||||
> TODO: Need to involve Charlie in how we want to talk about import declarations
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-macros-caps" %} name macros with **ALL_CAPITAL_SNAKE_CASE**.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-macros-form" %} prepend macro names with `AZ_<SERVICENAME>` to make macros unique.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-naming-macros-functions" %} use macros where an inline function or function template would achieve the same effect. Macros are not required for code efficiency.
|
||||
|
||||
{% highlight cpp %}
|
||||
// Bad
|
||||
##define MAX(a,b) ((a > b) ? a : b)
|
||||
|
||||
// Good
|
||||
template<class T>
|
||||
[[nodiscard]] inline T Max(T x, T y) {
|
||||
return x > y ? x : y;
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-naming-macros-donoevil" %} change syntax via macro substitution. It [makes the program unintelligible](https://gist.github.com/aras-p/6224951) to all but the perpetrator.
|
||||
|
||||
#### Type Safety Recommendations
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-rule-of-zero" %} In class types, implement the "rule of zero", the "rule of 3", or the "rule of 5". That is, of the special member functions, a type should implement exactly one of the following:
|
||||
|
||||
* No copy constructor, no copy assignment operator, no move constructor, no move assignment operator, or destructor.
|
||||
* A copy constructor, a copy assignment operator, no move constructor, no move assignment operator, and a destructor.
|
||||
* A copy constructor, a copy assignment operator, a move constructor, a move assignment operator, and a destructor.
|
||||
|
||||
This encourages use of resource managing types like std::unique_ptr (which implements the rule of 5) as a compositional tool in more complex data models that implement the rule of zero.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-initialize-all-data" %} provide types which are usable when default-initialized. (That is, every constructor must initialize all type invariants, not assume members have default values of 0 or similar.)
|
||||
|
||||
{% highlight cpp %}
|
||||
class TypeWithInvariants {
|
||||
int m_member;
|
||||
public:
|
||||
TypeWithInvariants() noexcept : member(0) {} // Good: initializes all parts of the object
|
||||
[[nodiscard]] int Next() noexcept {
|
||||
return m_member++;
|
||||
}
|
||||
};
|
||||
|
||||
class BadTypeWithInvariants {
|
||||
int m_member;
|
||||
public:
|
||||
BadTypeWithInvariants() {} // Bad: Does not initialize all parts of the object
|
||||
int Next() {
|
||||
return m_member++;
|
||||
}
|
||||
};
|
||||
|
||||
void TheCustomerCode() {
|
||||
TypeWithInvariants a{}; // value-initializes a TypeWithInvariants, OK
|
||||
TypeWithInvariants b; // default-initializes a TypeWithInvariants, we want this to be OK
|
||||
BadTypeWithInvariants c{}; // value-initializes a BadTypeWithInvariants, OK
|
||||
BadTypeWithInvariants d; // default-initializes a BadTypeWithInvariants, this will trigger
|
||||
// undefined behavior if anyone calls d.Next()
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-no-getters-or-setters" %} define getters and setters for data transfer objects. Expose the members directly to users unless you need to enforce some constraints on the data. For example:
|
||||
{% highlight cpp %}
|
||||
// Good - no restrictions on values
|
||||
struct ExampleRequest {
|
||||
int RetryTimeoutMs;
|
||||
const char* Text;
|
||||
};
|
||||
|
||||
// Bad - no restrictions on parameters and access is not idiomatic
|
||||
class ExampleRequest {
|
||||
int m_retryTimeoutMs;
|
||||
const char* m_text;
|
||||
public:
|
||||
[[nodiscard]] int GetRetryTimeoutMs() const noexcept {
|
||||
return m_retryTimeoutMs;
|
||||
}
|
||||
void SetRetryTimeoutMs(int i) noexcept {
|
||||
m_retryTimeoutMs = i;
|
||||
}
|
||||
[[nodiscard]] const char* GetText() const noexcept {
|
||||
return m_text;
|
||||
}
|
||||
void SetText(const char* i) noexcept {
|
||||
m_text = i;
|
||||
}
|
||||
};
|
||||
|
||||
// Good - type maintains invariants
|
||||
class TypeWhichEnforcesDataRequirements {
|
||||
size_t m_size;
|
||||
int* m_data;
|
||||
public:
|
||||
[[nodiscard]] size_t GetSize() const noexcept {
|
||||
return m_size;
|
||||
}
|
||||
void AddData(int i) noexcept {
|
||||
m_data\[m_size++\] = i;
|
||||
}
|
||||
};
|
||||
|
||||
// Also Good
|
||||
class TypeWhichClamps {
|
||||
int m_retryTimeout;
|
||||
public:
|
||||
[[nodiscard]] int GetRetryTimeout() const noexcept {
|
||||
return m_retryTimeout;
|
||||
}
|
||||
void SetRetryTimeout(int i) noexcept {
|
||||
if (i < 0) i = 0; // clamp i to the range [0, 1000]
|
||||
if (i > 1000) i = 1000;
|
||||
m_retryTimeout = i;
|
||||
}
|
||||
};
|
||||
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-optimize-position" %} declare variables in structures organized by use in a manner that minimizes memory wastage because of compiler alignment issues and size. All things being equal, use alphabetical ordering.
|
||||
|
||||
{% highlight cpp %}
|
||||
// Bad
|
||||
struct Foo {
|
||||
int A; // the compiler will insert 4 bytes of padding after A to align B
|
||||
char *B;
|
||||
int C;
|
||||
char *D;
|
||||
};
|
||||
|
||||
// Good
|
||||
struct Foo {
|
||||
int A; // C is now stored in that padding
|
||||
int C;
|
||||
char *B;
|
||||
char *D;
|
||||
};
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-enumsareinternal" %} use enums to model any data sent to the service. Use enums only for types completely internal to the client library. For example, an enum to disable Nagle's algorithm would be OK, but an enum to ask the service to create a particular entity kind is not.
|
||||
|
||||
#### Const and Reference members
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-no-const-or-reference-members" %} declare types with const or reference members. Const and reference members artificially make your types non-Regular as they aren't assignable, and have surprising interactions with C++ Core language rules. For example, many accesses to const or reference members would need to involve use of `std::launder` to avoid undefined behavior, but `std::launder` was added in C++17, a later version than the SDKs currently target. See C++ Core Working Group [CWG1116 "Aliasing of union members"](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1116), [CWG1776 "Replacement of class objects containing reference members"](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1776), and [P0137R1 "Replacement of class objects containing reference members"](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0137r1.html) for additional details.
|
||||
|
||||
If you want a type to provide effectively const data except assignment, declare all your member functions const. Const member functions only get a const view of the class' data.
|
||||
|
||||
{% highlight cpp %}
|
||||
// Bad
|
||||
class RetrySettings {
|
||||
const int m_maxRetryCount;
|
||||
public:
|
||||
int GetMaxRetryCount() {
|
||||
// intent: disallow m_maxRetryCount = aDifferentValue;
|
||||
return m_maxRetryCount;
|
||||
}
|
||||
};
|
||||
|
||||
// Good
|
||||
class RetrySettings {
|
||||
int m_maxRetryCount;
|
||||
public:
|
||||
int GetMaxRetryCount() const {
|
||||
// still prohibits m_maxRetryCount = aDifferentValue; without making RetrySettings un-assignable
|
||||
return m_maxRetryCount;
|
||||
}
|
||||
};
|
||||
{% endhighlight %}
|
||||
|
||||
#### Integer sizes
|
||||
|
||||
The following integer rules are listed in rough priority order. Integer size selections are primarily driven by service future compatibility. For example, just because today a service might have a 2 GiB file size limit does not mean that it will have such a limit forever. We believe 64 bit length limits will be sufficient for sizes an individual program works with for the foreseeable future.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-integer-files" %} Represent file sizes with `int64_t`, even on 32 bit platforms.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-integer-memory-buffers" %} Represent memory buffer sizes with `size_t` or `ptrdiff_t` as appropriate for the environment. Between the two, choose the type likely to need the fewest conversions in application. For example, we would prefer signed `ptrdiff_t` in most cases because signed integers behave like programmers expect more consistently, but the SDK will need to transact with `malloc`, `std::vector`, and/or `std::string` which all speak unsigned `size_t`.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-integer-service-values" %} Represent any other integral quantity passed over the wire to a service using `int64_t`, even if the service uses a 32 bit constant internally today.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-design-logical-integer-not-int" %} Use `int` under any circumstances, including `for` loop indexes. Those should usually use `ptrdiff_t` or `size_t` under the buffer size rule above.
|
||||
|
||||
{% include requirement/MAY id="cpp-design-logical-integer-otherwise" %} Use any integer type in the `intN_t` or `uintN_t` family as appropriate for quantities not enumerated above, such as calculated values internal to the SDK such as retry counts.
|
||||
|
||||
#### Secure functions
|
||||
|
||||
{% include requirement/SHOULDNOT id="cpp-design-logical-no-ms-secure-functions" %} use [Microsoft security enhanced versions of CRT functions](https://docs.microsoft.com/cpp/c-runtime-library/security-enhanced-versions-of-crt-functions) to implement APIs that need to be portable across many platforms. Such code is not portable and is not compatible with either the C or C++ Standards. See [arguments against]( http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm).
|
||||
|
||||
> TODO: Verify with the security team, and what are the alternatives?
|
||||
|
||||
#### Enumerations
|
||||
|
||||
{% include requirement/MUST id="cpp-design-logical-client-enumerations-no-enums" %} use `enum` or `enum class` for values shared "over the wire" with a service, to support future compatibility with the service where additional values are added. Such values should be persisted as strings in client data structures instead.
|
||||
|
||||
{% include requirement/MAY id="cpp-design-logical-client-enumerations-enumish-pattern" %} provide an 'extensible enum' pattern for storing service enumerations which provides reasonable constant values. This pattern stores the value as a string but provides public static member fields with the individual values for customer consumption. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
#include <azure/core/strings.hpp> // for Azure::Core::Strings::LocaleInvariantCaseInsensitiveEqual
|
||||
#include <utility> // for std::move
|
||||
namespace Azure { namespace Group { namespace Service {
|
||||
|
||||
// an "Extensible Enum" type
|
||||
class KeyType {
|
||||
std::string m_value;
|
||||
public:
|
||||
// Provide `explicit` conversion from string or types convertible to string:
|
||||
explicit KeyType(const std::string& value) : m_value(value) { }
|
||||
explicit KeyType(std::string&& value) : m_value(std::move(value)) { }
|
||||
explicit KeyType(const char* value) : m_value(value) { }
|
||||
|
||||
// Provide an equality comparison. If the service treats the enumeration case insensitively,
|
||||
// use LocaleInvariantCaseInsensitiveEqual to prevent differing locale settings from affecting
|
||||
// the SDK's behavior:
|
||||
bool operator==(const KeyType& other) const noexcept {
|
||||
return Azure::Core::Strings::LocaleInvariantCaseInsensitiveEqual(m_value, other.m_value);
|
||||
}
|
||||
|
||||
bool operator!=(const KeyType& other) const noexcept { return !(*this == other); }
|
||||
|
||||
// Provide a "Get" accessor
|
||||
const std::string& Get() const noexcept { return m_value; }
|
||||
|
||||
// Provide your example values as static const members
|
||||
const static KeyType Ec;
|
||||
const static KeyType EcHsm;
|
||||
const static KeyType Rsa;
|
||||
const static KeyType RsaHsm;
|
||||
const static KeyType Oct;
|
||||
};
|
||||
}}} // namespace Azure::Group::Service
|
||||
|
||||
|
||||
// in a .cpp file:
|
||||
namespace Azure { namespace Group { namespace Service {
|
||||
const KeyType KeyType::Ec{"EC"};
|
||||
const KeyType KeyType::EcHsm{"EC-HSM"};
|
||||
const KeyType KeyType::Rsa{"RSA"};
|
||||
const KeyType KeyType::RsaHsm{"RSA-HSM"};
|
||||
const KeyType KeyType::Oct{"OCT"};
|
||||
}}} // namespace Azure::Group::Service
|
||||
{% endhighlight %}
|
||||
|
||||
#### Physical Design
|
||||
|
||||
> TODO: Move this to implementation or move the headers discussion from implementation here
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-physical-include-quotes" %} include files using quotes (") for files within the same git repository, and angle brackets (<>) for external dependencies.
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-physical-unnamed-namespace" %} declare all types that are only used within the same `.cpp` file in an unnamed namespace. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
namespace {
|
||||
struct HashComputation {
|
||||
int InternalBookkeeping;
|
||||
};
|
||||
} // unnamed namespace
|
||||
{% endhighlight %}
|
||||
|
||||
#### Class Types (including `union`s and `struct`s)
|
||||
|
||||
Throughout this section, *class types* includes types with *class-key* `struct` or *class-key* `union`, consistent with the [C++ Standard](http://eel.is/c++draft/class#pre-4).
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-classes" %} name class types with **PascalCase**.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-classes-public-protected-variables" %} name `public` and `protected` member variables with **PascalCase**.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-classes-public-variables" %} name `private` member variables with an `m_` prefix, followed by a **camelCase** name. For example, `m_timeoutMs`.
|
||||
|
||||
{% include requirement/MUST id="cpp-design-naming-classes-functions" %} name member functions with **PascalCase**, except where the C++ Standard forbids this. For example, `UploadBlob`, or `operator[]`.
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-naming-classes-no-struct-keyword" %} declare classes with only public members using *class-key* `struct`.
|
||||
{% highlight cpp %}
|
||||
// Good
|
||||
struct OnlyPublicMembers {
|
||||
int Member;
|
||||
};
|
||||
|
||||
// Bad
|
||||
class OnlyPublicMembers {
|
||||
public:
|
||||
int Member;
|
||||
};
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/SHOULD id="cpp-design-naming-classes-typedefs" %} define class types without using `typedef`s. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
// Good: Uses C++ style class declaration:
|
||||
struct IotClient {
|
||||
char* ApiVersion;
|
||||
IotClientCredentials* Credentials;
|
||||
int RetryTimeout;
|
||||
};
|
||||
|
||||
// Bad: Uses C-style typedef:
|
||||
typedef struct IotClient {
|
||||
char* ApiVersion;
|
||||
IotClientCredentials* Credentials;
|
||||
int RetryTimeout;
|
||||
} AzIotClient;
|
||||
{% endhighlight %}
|
||||
|
||||
#### Tooling
|
||||
|
||||
We use a common build and test pipeline to provide for automatic distribution of client libraries. To support this, we need common tooling.
|
||||
|
||||
|
@ -354,231 +782,4 @@ endif()
|
|||
|
||||
{% include requirement/MUSTNOT id="cpp-tooling-cmake-no-samples-by-default" %} install samples by default.
|
||||
|
||||
## Packaging
|
||||
|
||||
{% include requirement/SHOULD id="cpp-package-dynamic" %} provide both dynamic and static linking options for your library. Each has its own merits and use cases.
|
||||
|
||||
{% include requirement/MUST id="cpp-package-source" %} publish your package in source code format. Due to differences in platforms, this is the most common publishing mechanism for C libraries.
|
||||
|
||||
{% include requirement/MUST id="cpp-package-vcpkg" %} publish your package to [vcpkg](https://github.com/Microsoft/vcpkg), a C++ library manager supporting Windows, Linux, and MacOS.
|
||||
|
||||
## Formatting
|
||||
|
||||
{% include requirement/MUST id="cpp-format-cpp" %} use [cpp-format](https://clang.llvm.org/docs/ClangFormat.html) for formatting your code. Use the [.clang-format](https://github.com/Azure/azure-sdk-for-cpp/blob/master/.clang-format) options.
|
||||
|
||||
In general, cpp-format will format your code correctly and ensure consistency. However, these are few additional rules to keep in mind.
|
||||
|
||||
{% include requirement/MUST id="cpp-format-cpp-loops" %} place all conditional or loop statements on one line, or add braces to identify the conditional/looping block.
|
||||
|
||||
{%highlight c %}
|
||||
if (meow == 0) purr += 1; // OK
|
||||
if (meow == 0) {
|
||||
purr += 1; // OK
|
||||
}
|
||||
if (meow == 0) { purr += 1; } // OK (although will probably be changed by cpp-format)
|
||||
if (meow == 0)
|
||||
purr += 1; // NOT OK
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-format-cpp-closing-braces" %} add comments to closing braces. Adding a comment to closing braces can help when you are reading code because you don't have to find the begin brace to know what is going on.
|
||||
|
||||
{% highlight cpp %}
|
||||
while (1) {
|
||||
if (valid) {
|
||||
...
|
||||
} /* if valid */
|
||||
else {
|
||||
|
||||
} /* not valid */
|
||||
} /* end forever */
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-format-cpp-closing-endif" %} add comments to closing preprocessor directives to make them easier to understand. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
#if _BEGIN_CODE_
|
||||
|
||||
#ifndef _INTERNAL_CODE_
|
||||
|
||||
#endif /* _INTERNAL_CODE_ */
|
||||
|
||||
#endif /* _BEGIN_CODE_ */
|
||||
{% endhighlight %}
|
||||
|
||||
|
||||
{% include requirement/SHOULDNOT id="cpp-format-cpp-space-return" %} use parens in return statements when it isn't necessary.
|
||||
|
||||
{% include requirement/MUST id="cpp-format-cpp-no-yoda" %} place constants on the right of comparisons. For example `if (a == 0)` and not `if (0 == a)`
|
||||
|
||||
{% include requirement/MUST id="cpp-format-cpp-comment-fallthru" %} include a comment for falling through a non-empty `case` statement. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
switch (...) {
|
||||
case 1:
|
||||
DoSomething();
|
||||
break;
|
||||
case 2:
|
||||
DoSomethingElse();
|
||||
/* fall through */
|
||||
case 3:
|
||||
{
|
||||
int v;
|
||||
|
||||
DoSomethingMore(v);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log(LOG_DEBUG, "default case reached");
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/SHOULDNOT id="cpp-format-cpp-no-goto" %} use `goto` statements. The main place where `goto` statements can be usefully employed is to break out of several levels of `switch`, `for`, or `while` nesting, although the need to do such a thing may indicate that the inner constructs should be broken out into a separate function with a success/failure return code. When a `goto` is necessary, the accompanying label should be alone on a line and to the left of the code that follows. The `goto` should be commented as to its utility and purpose.
|
||||
|
||||
## Complexity Management
|
||||
|
||||
{% include requirement/SHOULD id="cpp-init-all-vars" %} Initialize all variables. Only leave them
|
||||
uninitialized if there is a real performance reason to do so. Use static and dynamic analysis tools to
|
||||
check for uninitialized access. You may leave "result" variables uninitialized so long as they clearly do
|
||||
not escape from the innermost lexical scope.
|
||||
|
||||
{% include requirement/SHOULD id="cpp-function-size" %} limit function bodies to one page of code (40 lines, approximately).
|
||||
|
||||
{% include requirement/MUST id="cpp-document-null-bodies" %} document null statements. Always document a null body for a `for` or `while` statement so that it is clear the null body is intentional.
|
||||
|
||||
{% include requirement/MUST id="cpp-use-explicit-compares" %} use explicit comparisons when testing for failure. Use `if (FAIL != f())` rather than `if (f())`, even though FAIL may have the value 0 which C considers to be false. An explicit test will help you out later when somebody decides that a failure return should be -1 instead of 0.
|
||||
|
||||
Explicit comparison should be used even if the comparison value will never change. e.g. `if (!(bufsize % sizeof(int)))` should be written as `if (0 == (bufsize % sizeof(int))` to reflect the numeric (not boolean) nature of the test.
|
||||
|
||||
A frequent trouble spot is using `strcmp` to test for string equality. You should **never** use a default action. The preferred approach is to use an inline function:
|
||||
|
||||
{% highlight cpp %}
|
||||
inline bool StringEqual(char *a, char *b) {
|
||||
return (0 == strcmp(a, b));
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
~ Should
|
||||
{% include requirement/SHOULDNOT id="cpp-embedded-assign" %} use embedded assignments. There is a time and a place for embedded assignment statements. In some constructs, there is no better way to accomplish the results without making the code bulkier and less readable.
|
||||
|
||||
{% highlight cpp %}
|
||||
while (EOF != (c = getchar())) {
|
||||
/* process the character */
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
However, one should consider the tradeoff between increased speed and decreased maintainability that results when embedded assignments are used in artificial places.
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-test-implicit-assign" %} use implicit assignment inside a test. This is generally an accidental omission of the second `=` of the logical compare. The following is confusing and prone to error.
|
||||
|
||||
{% highlight cpp %}
|
||||
if (a = b) { ... }
|
||||
{% endhighlight %}
|
||||
|
||||
Does the programmer really mean assignment here? Sometimes yes, but usually no. Instead use explicit tests and avoid assignment with an implicit test. The recommended form is to do the assignment before doing the test:
|
||||
|
||||
{% highlight cpp %}
|
||||
a = b;
|
||||
if (a) { ... }
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-no-register" %} use the register keyword. Modern compilers will put variables in registers automatically.
|
||||
|
||||
{% include requirement/MUST id="cpp-be-const-correct" %} be `const` correct. C++ provides the `const` keyword to allow passing as parameters objects that cannot change to indicate when a method doesn't modify its object. Using `const` in all the right places is called "const correctness."
|
||||
|
||||
{% include requirement/MUST id="cpp-use-hashif" %} use `#if` instead of `#ifdef`. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
// Bad example
|
||||
#ifdef DEBUG
|
||||
TemporaryDebuggerBreak();
|
||||
#endif
|
||||
{% endhighlight %}
|
||||
|
||||
Someone else might compile the code with turned-of debug info like:
|
||||
|
||||
{% highlight cpp %}
|
||||
cc -c lurker.cc -DDEBUG=0
|
||||
{% endhighlight %}
|
||||
|
||||
Alway use `#if` if you have to use the preprocessor. This works fine, and does the right thing, even if `DEBUG` is not defined at all (!)
|
||||
|
||||
{% highlight cpp %}
|
||||
// Good example
|
||||
#if DEBUG
|
||||
TemporaryDebuggerBreak();
|
||||
#endif
|
||||
{% endhighlight %}
|
||||
|
||||
If you really need to test whether a symbol is defined or not, test it with the `defined()` construct, which allows you to add more things later to the conditional without editing text that's already in the program:
|
||||
|
||||
{% highlight cpp %}
|
||||
#if !defined(USER_NAME)
|
||||
#define USER_NAME "john smith"
|
||||
#endif
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-large-comments" %}
|
||||
Use `#if` to comment out large code blocks.
|
||||
|
||||
Sometimes large blocks of code need to be commented out for testing. The easiest way to do this is with an `#if 0` block:
|
||||
|
||||
{% highlight cpp %}
|
||||
void Example()
|
||||
{
|
||||
great looking code
|
||||
|
||||
#if 0
|
||||
many lines of code
|
||||
#endif
|
||||
|
||||
more code
|
||||
}
|
||||
{% endhighlight %}
|
||||
|
||||
You can't use `/**/` style comments because comments can't contain comments and a large block of your code will probably contain connects.
|
||||
|
||||
Do not use `#if 0` directly. Instead, use descriptive macro names:
|
||||
|
||||
{% highlight cpp %}
|
||||
#if NOT_YET_IMPLEMENTED
|
||||
#if OBSOLETE
|
||||
#if TEMP_DISABLED
|
||||
{% endhighlight %}
|
||||
|
||||
Always add a short comment explaining why it is commented out.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-" %} put data definitions in header files. For example, this should be avoided:
|
||||
|
||||
{% highlight cpp %}
|
||||
/* aheader.hpp */
|
||||
int x = 0;
|
||||
{% endhighlight %}
|
||||
|
||||
It's bad magic to have space consuming code silently inserted through the innocent use of header files. It's not common practice to define variables in the header file, so it will not occur to developers to look for this when there are problems. Instead, define the variable once in a source file and then use an `extern` statement to reference it in the header file.
|
||||
|
||||
{% include requirement/MUSTNOT id="cpp-no-magic-numbers" %} use magic numbers. A magic number is a bare naked number used in source code. It's magic because no-one will know what it means after a while. This significantly reduces maintainability. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
// Don't write this.
|
||||
if (19 == foo) { RefundLotsMoney(); }}
|
||||
else { HappyDaysIKnowWhyIAmHere(); }
|
||||
{% endhighlight %}
|
||||
|
||||
Instead of magic numbers, use a real name that means something. You can use `constexpr` for names. For example:
|
||||
|
||||
{% highlight cpp %}
|
||||
constexpr int WE_GOOFED = 19;
|
||||
|
||||
if (WE_GOOFED == foo) { RefundLotsMoney(); }
|
||||
else { HappyDaysIKnowWhyIAmHere(); }
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-check-syscall-errors" %} check every system call for an error return, unless you know you wish to ignore errors. For example, `printf` returns an error code but it is rarely relevant. Cast the return to (void) if you do not care about the error code.
|
||||
|
||||
{% highlight cpp %}
|
||||
(void)printf("The return value is ignored");
|
||||
{% endhighlight %}
|
||||
|
||||
{% include requirement/MUST id="cpp-include-errorstr" %} include the system error text when reporting system error messages.
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче