Práce s pamětí C++: chytré ukazatele a proč je použít

31. 7. 2024
Doba čtení: 10 minut

Sdílet

Autor: Depositphotos
Představíme „chytré“ ukazatele (smart pointers) v C++ a vysvětlíme důvody, proč je používat. Ačkoliv jazyk C++ podporuje používání ukazatelů podobně jako jazyk C, není to dobrý nápad. Zajímá vás proč?

Pravděpodobně již víme, že proměnné našeho programu jsou umístěny fyzicky v paměti počítače. Pokud vytvoříme proměnnou typu int32_t, kompilátor alokuje 4B prostoru v paměti (samozřejmě dochází i k nějakému zarovnání pro optimalizaci, ale to nyní zanedbáme). Co když nevíme, kolik paměti bude náš program potřebovat? První možností by bylo alokovat co největší množství paměti a pak jen využít tolik, kolik potřebujeme. To samozřejmě není praktické – je to plýtvání zdroji.

Představme si, že program pro grafickou úpravu by vždy alokoval několik GB paměti, jen aby měl jistotu, že je schopen pracovat i s větším obrázkem. Pokud spustíme několik instancí tohoto grafického programu, jen abychom upravili pár menších obrázků, vyčerpáme paměť v počítači.

Již velmi dlouho umožňují programovací jazyky alokovat paměť za běhu. Podle potřeby řekneme operačnímu systému, kolik bychom chtěli paměti a pokud jí OS má, tak nám jí přidělí. Jakmile paměť nepotřebujeme, můžeme jí vrátit (respektive, měli bychom).

K práci s takto „dynamicky alokovanou“ pamětí slouží ukazatele. Trik je v tom, že ukazatel má předem známou velikost – je to vlastně adresa na paměť, do které poté uložíme data (a velikost adresy je stejná pro jeden int i pro instanci třídy o několika GB).

V programovacím jazyku C++ bylo dynamické alokování paměti značně zjednodušeno oproti programovacímu jazyku C. Jednoduchý příklad alokace paměti vidíme v následujícím kódu:

#include
#include

int main()
{
    int *p = new int;
    *p = 10;
    delete p;
    return 0;
}

Při běhu programu, kdy dojde na alokování paměti klíčovým slovem new  , si systém sám spočte, kolik bajtů paměti potřebujeme (ví, jak velký je datový typ int). To je značné zlepšení oproti C, kde bylo třeba funkci malloc() předat celkový počet bajtů, které chceme alokovat (příklad volání: (int*)malloc(sizeof(int))).

Kde je tedy problém?

Práce s ukazateli vypadá na první pohled triviálně. Bohužel rostoucí velikost programů a počet lidí na programech pracujících (a jejich rozdílné schopnosti) vedou k nesprávnému použití ukazatelů. Chyby způsobené tímto nesprávným použitím se mohou projevit až za delší dobu a nemusí být snadné je odhalit. Pojďme si jednotlivé typy chyb projít.

Memory leak (únik paměti)

Velmi často může docházet k neuvolnění alokované paměti (nepoužitím klíčového slova delete) nebo nesprávnému uvolnění ( delete vs delete[]). Tato špatně uvolněná paměť se označuje jako „memory leak“ (leak = únik). Následující ukázka kódu memory leak demonstruje.

int main()
{
    //cast 1:
    int *p1;
    for (int i=0; i < 10; ++i)
    {
        p1 = new int;
        *p1 = i;
    }

    //cast 2:
    int *p2 = new int[10];
    delete p2; //spravne ma byt delete[] p2; protoze uvolnujeme alokovane pole
    return 0;
}

V předchozí ukázce alokuje for cyklus novou paměť do ukazatele p1, aniž by uvolnil paměť na kterou p1 ukazuje. Pokud program poběží dost dlouho, může zcela vyčerpat zdroje systému.

V druhé části kódu vidíme alokaci pole integerů do ukazatele p2. Bohužel nedojde ke korektnímu uvolnění paměti (pole se správně uvolňuje operátorem delete[].

Dangling pointer (visící pointer)

Dalším typem chyby bývá ukazatel ukazující do již neplatné paměti. Program bude nadále fungovat, ale pouze do doby, dokud se někdo nepokusí tento ukazatel dereferencovat (přistoupit na paměť, na kterou ukazuje). Tato chyba se většinou stává pokud dva ukazatele ukazují na stejné místo v paměti a na jeden z nich použijeme operátor delete bez toho, aniž bychom druhý nastavili na hodnotu NULL (v C++ by se měla používat hodnota nullptr). Příklad je v následující ukázce kódu.

int* foo()
{
    int a = 42;
    return &a;
}

int main()
{
    int *p = foo();

    int *p1 = new int;
    *p1 = 7;
    int *p2 = p1;
    delete p1;

    return 0;
}

Po zavolání funkce foo() máme v ukazateli p adresu proměnné a z funkce foo(). Tato paměť však již neexistuje (zanikla při výstupu z funkce). Ukazujeme tedy na neplatnou paměť.

Wild pointer (divoké ukazatele)

Jméno pravděpodobně odkazuje na divoké zvíře, od kterého nevíme co čekat – stejně jako od tohoto ukazatele. Pokud založíme proměnnou typu ukazatel, může v ní být neinicializovaná hodnota. Pokud bychom tento ukazatel poté dereferencovali (pokusili se na něj přistoupit), velmi pravděpodobně dojde k chybě v běhu programu (přístup na nevalidní paměť).

Malá poznámka: protože jsou neinicializované proměnné problém (a bezpečnostní riziko), je jejich použití považováno za chybu a ne varování. Aby bylo možné kód vyzkoušet, je třeba najít správná nastavení pro vaše vývojové prostředí. Například ve VisualStudio na OS Windows je třeba ve vlastnostech projektu vypnout „SDL“ a v sekci „C/C++“ podsekce „Code Generation“ nastavit „Disable Security Check“.

int main()
{
    int* p;
    std::cout << std::hex << "0x" << (uint64_t)p << std::endl;
    //vytisklo 0x7fff171e0699, hodnota u vas muze byt jina
    return 0;
}

V předchozí ukázce vidíme neinicializovaný ukazatel na typ int. Zkusíme vypsat jeho hodnotu a získáme „divné číslo“. Poznámka: je dost možné, že získáte i hodnotu 0 – záleží jaký kompilátor používáte.

Buffer overflow (přetečení bufferu)

Poslední z přehlídky chyb plynoucích ze špatného použití ukazatelů je přetečení bufferu. Již víme, že data jsou v počítači v jakýchsi „chlívcích“ pěkně za sebou. Co nám tedy teoreticky brání zkusit přes ukazatel přejít na předchozí/následující hodnotu?

V následující ukázce vidíme, že ukazatel p je nastaven na adresu proměnné a. Protože se jedná o ukazatel, nic nám nebrání použít operátor []. Tím jsme schopní přečíst hodnotu proměnné b.

int main()
{
    int a = 5;
    int b = 123;
    int* p = &a;

    std::cout << p[1] << std::endl;

    return 0;
}

Chytré ukazatele

Chytré ukazatele (anglicky „smart pointers“) jsou způsob, jak výše zmiňovaným chybám předejít. Využívají toho, že C++ vždy zavolá desktruktor objektu, pokud objekt „passes out of scope“, což lze přeložit jako „skončí jeho životnost“ nebo je explicitně zavolán operátor  delete.

Zavolání destruktoru je garantováno, a proto lze do destruktoru přidat uvolnění paměti. Vznikne garance, že paměť je uvolněna automaticky.

std::unique_ptr

Jak název napovídá, je std::unique_ptr „unikátní“. Třída std::unique_ptr nemá implementovaný kopírovací konstruktor a operátor přiřazení. Tím je zajištěno, že jakákoliv špatná práce s tímto smart pointerem je detekována již při kompilaci.

#include <memory>
#include <iostream>

int main()
{
    std::unique_ptr p0(new int);
    std::cout << p0.get() << std::endl;
    std::cout << &p0 << std::endl;

    std::unique_ptr p1(new int);
    *p1 = 42;

    return 0;
}
#include <memory>

int main()
{
    std::unique_ptr p1(new int);

    std::unique_ptr p2 = p1;
    std::unique_ptr p3(p1);

    return 0;
}
#include &memory>

int main()
{
    std::unique_ptr p1(new int);

    std::unique_ptr p2 = std::move(p1);

    return 0;
}

První ukázka kódu ukazuje základní použití std::unique_ptr. Vytvoříme dva objekty std::unique_ptr, jedná se unikátní ukazatel na datový typ int. První objekt jménem p0 použijeme pouze k vypsání jeho adresy metodou .get(). Zároveň si vypíšeme adresu samotného objektu p0  – vidíme že se liší od ukazatele získaného metodou .get(), to je samozřejmě správně – std::unique_ptr je objekt obalující původní ukazatel.

V první ukázce také vidíme objekt jménem p1, u něj vidíme, jak se do objektu přiřadí hodnota. K tomu se použije přetížený operátor * (operátor dereference).

Ve druhé ukázce vidíme, jak nepředávat objekt std::unique_ptr. Přiřazení operátorem = selže již při kompilaci, protože by tím došlo k překopírování adresy a tím by na jeden objekt ukazovaly dva ukazatele a to nechceme (objekt se nejmenuje „unikátní“ jen tak).

I pokus o zavolání kopírovacího konstruktoru (vytváření objektu p3) skončí chybou kompilace – opět ze stejného důvodu.

Ve třetí ukázce vidíme, jak správně předat objekt std::unique_ptr  přesunutím (anglicky „move“). Tím se hodnota nezkopíruje, ale přesune (nevzniknou dva ukazatele, zůstane jen jeden).

std::shared_ptr

Pokud potřebujeme, aby na objekt existovalo více ukazatelů, je vhodné použít std::shared_ptr, tento smart pointer udržuje počet referencí na daný objekt. Každé přiřazení/kopírovací konstruktor zvedá počítadlo o jeden a každé zavolání destruktoru počítadlo sníží. Pokud počítadlo dosáhne hodnoty 0, je objekt zničen.

#include
#include

int main()
{

    std::shared_ptr p0(new int);
    std::shared_ptr p1 = std::make_shared();

    *p1 = 3;
    *p0 = 4;

    std::cout << "Pocet objektuv p0 = " << p0.use_count() << std::endl;
    std::cout << "Pocet objektuv p1 = " << p1.use_count() << std::endl;
    return 0;
}

Kód výše je pouze ukázkou, jak vytvořit a použít objekt std::shared_ptr. Poté si vypíšeme hodnotu počítadla referencí (metoda .use_count() ). Přístup k objektu je realizován operátorem dereference (stejně jako v případě std::unique_ptr).

#include <memory>
#include <iostream>

int main()
{

    std::shared_ptr<int> p0(new int);

    *p0 = 4;

    std::cout << "Pocet objektuv p0 = " << p0.use_count() << std::endl;

    std::shared_ptr<int> p3 = p0;
    std::shared_ptr<int> p4(p0);
    std::cout << "Pocet objektuv p0 = " << p0.use_count() << std::endl;

    {
        std::shared_ptr<SFoo> p6(p0);
        std::shared_ptr<SFoo> p7(p3);
        std::cout << "Pocet objektuv p0 = " << p0.use_count() << std::endl;
        std::cout << "Konec scope" << std::endl;
    }
    std::cout << "Pocet objektuv p0 = " << p0.use_count() << std::endl;

    return 0;
}

V kódu výše vidíme, jak použití kopírovacího konstruktoru a operátoru přiřazení zvyšuje hodnotu počítadla referencí a jak konec scope naopak počítadlo referencí snižuje. Druhé zmiňované lze vidět u objektů p6 a p7. Tyto objekty jsou umístěné mezi novými složenými závorkami – to vytvoří extra scope. To lze využít pro mnoho užitečných věcí – mimo jiné lze znovu použít jméno proměnné.

#include <memory>
#include <iostream>

int main()
{

    std::shared_ptr<int> p0(new int);
    *p0 = 4;

    std::shared_ptr<int> p3 = p0;
    std::shared_ptr<int> p4(p0);

    std::cout << "Hodnota *p0 == " << *p0 << std::endl;
    std::cout << "Hodnota *p4 == " << *p4 << std::endl << std::endl;
    *p4 = 8;
    std::cout << "Hodnota *p0 == " << *p0 << std::endl;
    std::cout << "Hodnota *p4 == " << *p4 << std::endl;
    return 0;
}

Tento kód demonstruje jistou vlastnost, na kterou je třeba si dát pozor. Třída std::shared_ptr neimplementuje COW (princip „copy-on-write“). Změna, kterou provedete na objektu, je viditelná i z ostatních objektů.

std::weak_ptr

Tento třetí typ smart pointeru realizuje „slabou referenci“. Občas se hodí udržovat ukazatel na nějaký objekt bez toho, aniž bychom s objektem pracovali (například nás zajímá, jestli stále existují v paměti konkrétní data).

Objekt, na který máme slabou referenci, může být kdykoliv uvolněn, my jen musíme být schopni to detekovat a k tomu se std::weak_ptr  hodí.

Další vhodné použití std::weak_ptr je pro rozbití „kruhové závislosti“. Představme si frontu realizovanou pomocí kruhu s std::shared_ptr. Pokud bychom chtěli kruh rozpojit, máme problém (všechny std::shared_ptr mají závislost navzájem). Použitím jednoho std::weak_ptr neexistuje kruhová závislost (rozbili jsme ji) a celou strukturu můžeme zničit.

Smart pointer std::weak_ptr si lze představit jako std::shared_ptr, který nezvyšuje počítadlo referencí.

Je důležité zmínit, že s objektem obaleným v std::weak_ptr  lze pracovat pouze po konverzi na std::shared_ptr.

#include <iostream>
#include <memory>

void kontrola_weak_ptr(const std::weak_ptr& wp)
{
    if (wp.use_count())
        std::cout << "Objekt existuje" << std::endl;
    else
        std::cout << "Objekt jiz neexistuje" << std::endl;
}

void pouzij_weak_ptr(const std::weak_ptr& wp)
{
    if (std::shared_ptr spt = wp.lock())
        std::cout << "*spt == " << *spt << std::endl;
    else
        std::cout << "gw is expired\n";
}

int main()
{
    std::weak_ptr gw;
    {
        auto sp = std::make_shared(42);
        gw = sp;

        kontrola_weak_ptr(gw);
        pouzij_weak_ptr(gw);
    }

    kontrola_weak_ptr(gw);
    pouzij_weak_ptr(gw);
    return 0;
}

Tento kód vytvoří objekt gw typu std::weak_ptr. Poté v extra scope vytvoříme std::shared_ptr a pomocí operátoru přiřazení přiřadíme do gw. Dokud jsme v tomto scopu, zavoláme dvě různé funkce. Ta první zkontroluje platnost objektu pomocí metody  .use_count().

Druhá funkce se pokusí vytvořit ze std::weak_ptr objekt typu std::shared_ptr (jak jsem zmínil výše, bez toho nelze s objektem obaleným std::weak_ptr pracovat). Tato konverze se provádí voláním metody  .lock().

prace_s_linuxem_tip

Volání obou funkcí provedeme i mimo skope a můžeme vidět, že neexistence objektu je detekována.

Doufám, že dnešní článek dostatečně shrnul důvody, proč smart pointery používat a zároveň bude nápovědou, jak je používat.

Autor článku

Začínal na programovacích jazycích Pascal a PHP. Na ČVUT FIT se zamiloval do low-level C, které používá pro programování MCU. Programovací jazyk C++ považuje za dokonalý a rád by ho uměl pořádně.

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »