Skip to content

A lightweight lazy-loading wrapper written in Modern C++

License

Notifications You must be signed in to change notification settings

bitwizeshift/Lazy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lazy

Build Status Build status Github Issues Tested Compilers Documentation GitHub License Github Releases

What is Lazy<T>?

Lazy<T> is a lightweight lazy-loading wrapper around any arbitrary class T written in modern C++.

All Lazy objects have their construction deferred until the latest possible time to ensure the laziest construction. Upon invocation of a member function, the Lazy<T> will instantiate type T by forwarding T the arguments originally supplied with the construction of the Lazy.

Here's an example of Lazy in action:

auto lazy_string = lazy::make_lazy<std::string>("Hello world!");
// Creates Lazy object, doesn't construct std::string

// ... do other operations

std::cout << *lazy_string << "\n"; 
//           ^----------^
//           Constructs the std::string here for first use here

lazy_string->resize(10); // Already constructed, using the same std::string

It's good to be lazy!

This library has been written with an emphasis on performance, and aims to reduce unnecessary overhead due to redundant instantiations or duplications. In order to achieve this, deferred argument construction takes copies of all arguments to be passed to the constructor and, at construction time, will use move semantics to invoke the construction of the object. This reduces the need to pay for the cost of duplicate copies, to a cost of copy-and-move instead.

Why be lazy?

Certain objects may be expensive to construct, especially when they perform complex algorithms, async callbacks, and connections to outside systems. In situations where these objects may not be used within all possible execution paths, this can result in wasted cpu cycles.

Often, a lazy-initialization pattern is implemented within the class for the specific-case where it is needed, resulting in a lot of boilerplate code, and possible areas of failure. Lazy<T> seeks to make it easier to lazily-initialize entries by handling all of the work with a simple tested API that behaves like a standard c++ smart pointer.

How to Use

Inclusion

Lazy<T> is a header-only library, making it easy to drop into any new project.

To start using, add a -I to the include/ directory, and include the header lazy/Lazy.hpp from the project.

#include <lazy/Lazy.hpp>

// use lazy::Lazy<T> 

Lazy<T> Objects

All Lazy objects have operator-> and operator* overloaded, making them behave similar to a smart pointer or an iterator. The underlying type will remain uninstantiated until such time that certain functions are accessed -- such as when the operators -> or * are accessed. At which point it will attempt a construction of the underlying type T.

This means that the type managed by a Lazy does not require a default or trivial constructor in order for this to properly operate; it simply requires a function to inform it how to instantiate the type at a later point.

Construction

There are various ways to construct lazy objects through Lazy<T> depending on the required needs.

  1. Default construction
  2. make_lazy<T> utility for forwarding arguments
  3. Function delegation for manually specifying construction/destruction behavior
  4. Copy/Move construction/assignment

1. Default Construction

If you have a type that is to be lazily constructed with the default or trivial constructor, use the default constructor for the Lazy class. This isn't particularly exciting, but this is the default behavior.

auto lazy_foo = lazy::Lazy<Foo>();

// ...

lazy_foo->do_something(); // constructs Foo object with Foo()

2. make_lazy<T> Utility Function

If no custom logic is required to construct the type T other than the constructor arguments, then you can easily make use of the lazy::make_lazy<T>(...) utility function.

make_lazy behaves similarly to std::make_shared and std::make_unique in that it forwards its arguments to the underlying type. The difference is that these arguments are stored until which time that the Lazy object is constructed for its first use.

Note that this means that arguments supplied to make_lazy will require a copy constructor, since copies are used. This is necessary to avoid dangling reference problems when the values passed go out of scope prior to construction of the object.

An example of using make_lazy:

auto lazy_string = lazy::make_lazy<std::string>("Hello World",5);

// do something

std::cout << *lazy_string << std::endl; 
//           ^~~~~~~~~~~^
//           Constructs std::string with the constructor std::string(const char*, size_t)

3. Function delegation

If more complex logic is required for the construction of the T object, you can supply a construction (and optionally destruction) function-like object (function pointer, member-function,functor, or lambda).

The construction function simple must return a std::tuple containing the types that will be forwarded to the constructor (I recommend making use of std::make_tuple to simplify this).

The destruction function must take type T& as a parameter and return void. The purpose of the destruction function is to give the chance to clean up anything that may not be managed by a destructor (or, in the case of using fundamental types like pointers, the chance to delete). The destruction function will be called prior to calling T's destructor.

An example of where this may become useful:

auto open_file = [](){
  return std::make_tuple(fopen("some/file/path","r"));
};
auto close_file = [](FILE* ptr){
  fclose(ptr);
};
auto lazy_file = lazy::Lazy<FILE*>(open_file,close_file);

// somewhere else
use_file(*lazy_file); // constructs the lazy file object

4. Copy/Move Constructor/Assignment

A Lazy object is able to be constructed out of an instance of the underlying type T through copy or move construction. The same can also be done with instances of Lazy<T> as well.

In the case of T objects, the types will be used for deferred construction later on through a call to the copy or move constructors. In the case of Lazy<T> objects, they are constructed immediately, provided the Lazy being copied or moved has also itself been instantiated. If it is not, only the construction/destruction functions are copied or moved.

Similarly, the Lazy objects can be assigned to other Lazy objects, or directly to the type T. For assignments to type T, if the lazy is not already constructed, it will lazily construct itself prior to assignment to avoid the need for a copy or move constructor. For assignments to type Lazy, if the lazy being assigned is already initialized, the value will be assigned to the currently constructed Lazy. If it is uninitialized, only the constructor to the type will be assigned to defer instantiation until later use.

Examples:

auto a = lazy::make_lazy<std::string>("Hello world");
auto b = a; // copy construction

## Tested Compilers

The following compilers are currently being tested through continuous integration with Travis.

Note that Lazy only works on compiler that provide proper conformance for c++11, meaning this does not properly work on g++ before 4.8

Compiler Operating System
g++ 4.9.3 Ubuntu 14.04.3 TLS
g++ 5.3.0 Ubuntu 14.04.3 TLS
g++ 6.1.1 Ubuntu 14.04.3 TLS
clang 3.5.0 Ubuntu 14.04.3 TLS
clang 3.6.2 Ubuntu 14.04.3 TLS
clang 3.8.0 Ubuntu 14.04.3 TLS
clang xcode 6.0 Darwin Kernel 13.4.0 (OSX 10.9.5)
clang xcode 6.1 Darwin Kernel 14.3.0 (OSX 10.10.3)
clang xcode 7.0 Darwin Kernel 14.5.0 (OSX 10.10.5)
clang xcode 7.3 Darwin Kernel 15.5.0 (OSX 10.11.5)
clang xcode 8.0 Darwin Kernel 15.6.0 (OSX 10.11.6)
Visual Studio 14 2015 Windows Server 2012 R2 (x64)

## License

The class is licensed under the MIT License:

Copyright © 2016 Matthew Rodusek

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

A lightweight lazy-loading wrapper written in Modern C++

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages